Java新特性之Optional類超詳細(xì)介紹
前言
Optional 類是 Java 8 才引入的,Optional 是個(gè)容器,它可以保存類型 T 的值,或者僅僅保存 null。Optional 提供了很多方法,這樣我們就不用顯式進(jìn)行空值檢測(cè)。Optional 類的引入很好的解決空指針異常。
Java 8 引入 Optional 類,用來解決 NullPointerException。 Optional 代替
if…else
解決空指針問題,使代碼更加簡(jiǎn)潔。
1、Optional類概述
1.1、Optional類介紹
Optional 類是 Java 8 才引入的,Optional 是個(gè)容器,它可以保存類型 T 的值,或者僅僅保存 null。Optional 提供了很多方法,這樣我們就不用顯式進(jìn)行空值檢測(cè)。Optional 類的引入很好的解決空指針異常。
Java 8 引入 Optional 類,用來解決 NullPointerException。 Optional 代替 if…else
解決空指針問題,使代碼更加簡(jiǎn)潔。
1.2、Java8之前的空指針異常判斷
Java 在使用對(duì)象過程中,訪問任何方法或?qū)傩远伎赡軐?dǎo)致 NullPointerException:
例如我們通過以下方法,獲取存在 student 對(duì)象中的 Age 值。
public String getIsocode (Student student){ return student.getAge(); }
在這樣的示例中,如果我們想要避免由 student
或 student.age
為空而導(dǎo)致的空指針問題,我們就需要采用防御式檢查減少 NullPointerException(在訪問每一個(gè)值之前對(duì)其進(jìn)行明確地檢查):
public String getIsocode (Student student){ if (null == student) { // doSomething return "Unknown"; } if (null = student.getAge()) { // doSomething return "Unknown"; } return student.getAge(); }
然而,這種方案并不是很理想,因?yàn)闉榇藭?huì)多出多個(gè)不同的退出點(diǎn)(return),使得代碼維護(hù)變得艱難,之后每個(gè)可能的 null 檢查都會(huì)新增一個(gè)退出點(diǎn)。
為了簡(jiǎn)化這個(gè)過程,我們來看看用 Optional 類是怎么做的。
1.3、Java8之后Optional的使用
當(dāng)需要判斷的量多時(shí),此時(shí)的這些判斷語句可能會(huì)導(dǎo)致代碼臃腫冗余,為此 Java8 特意推出了 Optional 類來幫助我們?nèi)ヌ幚砜罩羔槷惓!?/p>
下面是 Optional 的一些基本用法:
@Data public class Student { private Integer age; } --- public class Test { public static void main(String[] args) { // 假設(shè) student 這個(gè)對(duì)象從數(shù)據(jù)庫(kù)中查出的 Student student = getStudent(); // 創(chuàng)建一個(gè)可接受 null 的 Optiona l類 Optional<Student> optional = Optional.ofNullable(student); // 用法1:獲取 student 對(duì)象中的某個(gè)值,如果不存在的話則取默認(rèn)值(不具有短路作用) Integer a1 = optional.map(Student::getAge).orElse(20)); // 用法2:獲取 student 對(duì)象中的某個(gè)值,如果不存在的話則取默認(rèn)值(具有短路作用,因?yàn)槭菓屑虞d) Integer a2 = optional.map(Student::getAge).orElseGet(() -> Integer.MAX_VALUE); // 用法3:判斷對(duì)象是否存在,不存在則拋出異常 optional.orElseThrow(() -> new RuntimeException("student不存在!")); // 用法4:判斷對(duì)象是否存在,存在的話對(duì)對(duì)象進(jìn)行操作,例如給對(duì)象賦初始值 optional.ifPresent(o -> o.setAge(18)); // 用法5:對(duì)象存在時(shí),且年齡滿足一定條件容器才會(huì)繼續(xù)保存這對(duì)象,否則將會(huì)剔除 optional.filter(o -> o.getAge() > 10); } }
2、Optional類使用
2.1、Optional類常用方法總結(jié)
方法 | 描述 |
---|---|
empty | 返回一個(gè)空的 Optional 實(shí)例 |
filter | 如果值存在并且滿足提供的謂詞,就返回包含該值的 Optional 對(duì)象;否則返回一個(gè)空的 Optional 對(duì)象 |
flatMap | 如果值存在,就對(duì)該值執(zhí)行提供的 mapping 函數(shù)調(diào)用,返回一個(gè) Optional 類型的值,否則就返回一個(gè)空的 Optional 對(duì)象 |
get | 如果該值存在,將該值用 Optional 封裝返回,否則拋出一個(gè) NoSuchElementException 異常 |
ifPresent | 如果值存在,就執(zhí)行使用該值的方法調(diào)用,否則什么也不做 |
isPresent | 如果值存在就返回 true,否則返回 false |
map | 如果值存在,就對(duì)該值執(zhí)行提供的mapping 函數(shù)調(diào)用 |
of | 將指定值用 Optional 封裝之后返回,如果該值為 null,則拋出一個(gè) NullPointerException 異常 |
ofNullable | 將指定值用 Optional 封裝之后返回,如果該值為 null,則返回一個(gè)空的 Optional 對(duì)象 |
orElse | 如果有值則將其返回,否則返回一個(gè)默認(rèn)值 |
orElseGet | 如果有值則將其返回,否則返回一個(gè)由指定的 Supplier 接口生成的值 |
orElseThrow | 如果有值則將其返回,否則拋出一個(gè)由指定的 Supplier 接口生成的異常 |
2.2、Optional對(duì)象創(chuàng)建
2.2.1、Optional.empty()方法
使用 Optional.empty()
方法聲明一個(gè)空的 Optional:
// 通過靜態(tài)工廠方法 Optional.empty(),創(chuàng)建一個(gè)空的 Optional 對(duì)象 Optional<Student> optStudent = Optional.empty();
2.2.2、Optional.of(T t)方法
使用 Optional.of(T t)
方法創(chuàng)建一個(gè)包含非空值的 Optional 對(duì)象 (不推薦):
// 靜態(tài)工廠方法 Optional.of(T t),依據(jù)一個(gè)非空值創(chuàng)建一個(gè) Optional 對(duì)象 Optional<Student> optStudent = Optional.of(student);
如果 student 為 null,這段代碼會(huì)立即拋出一個(gè) NullPointerException,而不是等到訪問 student 的屬性值時(shí)才返回一個(gè)錯(cuò)誤。
2.2.3、Optional.ofNullable(T t)方法
使用 Optional.ofNullable(T t)
方法創(chuàng)建一個(gè)包含可能為空的值的 Optional 對(duì)象 (推薦):
// 用靜態(tài)工廠方法 Optional.ofNullable(T t),你可以創(chuàng)建一個(gè)允許 null 值的 Optional 對(duì)象 Optional<Student> optStudent = Optional.ofNullable(student);
2.3、Optional對(duì)象獲取
2.3.1、get()方法
get()
方法,如果變量存在,它直接返回封裝的變量值,否則就拋出一個(gè) NoSuchElementException 異常,不推薦使用:
optional.map(Student::getAge).get()
2.3.2、orElse(T other)方法
orElse(T other)
方法,它允許你在 Optional 對(duì)象不包含值時(shí)提供一個(gè)默認(rèn)值:
optional.map(Student::getAge).orElse(20));
2.3.3、orElseGet(Supplier<? extends T> other)方法
orElseGet(Supplier<? extends T> other)
方法,它是 orElse 方法的延遲調(diào)用版,Supplier 方法只有在 Optional 對(duì)象不含值時(shí)才執(zhí)行調(diào)用(懶加載):
optional.map(Student::getAge).orElseGet(() -> Integer.MAX_VALUE);
2.3.4、orElseThrow(Supplier<? extends X> exceptionSupplier)方法
orElseThrow(Supplier<? extends X> exceptionSupplier)
方法,它和 get 方法非常類似,它們?cè)庥?Optional 對(duì)象為空時(shí)都會(huì)拋出一個(gè)異常,但是使用 orElseThrow 可以定制希望拋出的異常類型:
optional.orElseThrow(() -> new RuntimeException("student不存在!"));
2.3.5、ifPresent(Consumer<? super T> consumer)方法
ifPresent(Consumer<? super T> consumer)
方法,它讓能在變量值存在時(shí)執(zhí)行一個(gè)作為參數(shù)傳入的方法,否則就不進(jìn)行任何操作:
optional.ifPresent(o -> o.setAge(18));
2.4、Optional對(duì)象中值的提取和轉(zhuǎn)換
2.4.1、map()方法
map()
方法,如果值存在,就對(duì)該值執(zhí)行提供的 mapping 函數(shù)調(diào)用,如果值不存在,則返回一個(gè)空的 Optional 對(duì)象。
引入 Optional 以前:
String name = null; if(insurance != null){ name = insurance.getName(); }
引入 Optional 以后:
Optional<String> name = Optional.ofNullable(insurance).map(Insurance::getName);
Optional 的 map 方法和 Java 8 中 Stream 的 map 方法相差無幾。
2.4.2、flatMap()方法
flatMap()
方法,對(duì)于嵌套式的 Optiona 結(jié)構(gòu),我們應(yīng)該使用 flatMap 方法,將兩層的 Optional 合并成一個(gè)。
我們?cè)囍貥?gòu)以下代碼:
public String getCarInsuranceName(Person person) { return person.getCar().getInsurance().getName(); }
由于我們剛剛學(xué)習(xí)了如何使用 map,我們的第一反應(yīng)可能是我們可以利用 map 重寫之前的代碼:
Optional<Person> optPerson = Optional.of(person); Optional<String> name = optPerson.map(Person::getCar) .map(Car::getInsurance) .map(Insurance::getName);
不幸的是,這段代碼無法通過編譯。為什么呢? optPerson 是 Optional<Person>
類型的 變量, 調(diào)用 map 方法應(yīng)該沒有問題。但 getCar 返回的是一個(gè) Optional<Car>
類型的對(duì)象,這意味著 map 操作的結(jié)果是一個(gè) Optional<Optional<Car>>
類型的對(duì)象。因此,它對(duì) getInsurance 的調(diào)用是非法的。
下面應(yīng)用 map 和 flatMap 對(duì)上述示例進(jìn)行重寫:
public String getCarInsuranceName(Optional<Person> person) { return person.flatMap(Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("Unknown"); // 如果Optional的結(jié)果 值為空設(shè)置默認(rèn)值 }
2.5、Optional對(duì)象其他方法
2.5.1、isPresent()方法
可以使用 isPresent()
方法檢查 Optional 對(duì)象是否包含非空值,例如:
Optional<String> optional = Optional.of("Hello World"); if (optional.isPresent()) { System.out.println(optional.get()); }
2.5.2、filter()方法
filter()
方法接受一個(gè)謂詞作為參數(shù)。如果 Optional 對(duì)象的值存在,并且它符合謂詞的條件,filter 方法就返回其值,否則它就返回一個(gè)空的 Optional 對(duì)象。
比如,你可能需要檢查保險(xiǎn)公司的名稱是否為 “Cambridge-Insurance”。
Insurance insurance = ...; if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){ System.out.println("ok"); }
使用 Optional 對(duì)象的 filter 方法,這段代碼可以重構(gòu)如下:
Optional<Insurance> optInsurance = ...; optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName())) .ifPresent(x -> System.out.println("ok"));
3、Optional注意事項(xiàng)
3.1、Optional的序列化問題
由于 Optiona l類設(shè)計(jì)時(shí)就沒特別考慮將其作為類的字段使用,所以它也并未實(shí)現(xiàn) Serializable 接口。由于這個(gè)原因,如果你的應(yīng)用使用了某些要求序列化的庫(kù)或者框架,在域模型中使用Optional,有可能引發(fā)應(yīng)用程序故障。
然而,我們相信,通過前面的介紹,我們已經(jīng)看到用 Optional 聲明域模型中的某些類型是個(gè)不錯(cuò)的主意,尤其是你需要遍歷有可能全部或部分為空,或者可能不存在的對(duì)象時(shí)。如果你一定要實(shí)現(xiàn)序列化的域模型,作為替代方案, 我們建議你像下面這個(gè)例子那樣,提供一個(gè)能訪問聲明為 Optional、變量值可能缺失的接口,代碼清單如下:
public class Person { private Car car; public Optional<Car> getCarAsOptional() { return Optional.ofNullable(car); } }
3.2、避免使用基礎(chǔ)類型的 Optional 對(duì)象
Optional 提供了的一些基礎(chǔ)類型 —— OptionalInt
、OptionalLong
以及 OptionalDouble
,但不推薦大家使用基礎(chǔ)類型的 Optional,因?yàn)榛A(chǔ)類型的 Optional 不支持 map、 flatMap 以及 filter 方法,而這些卻是 Optional 類常用的方法??梢允褂?Optional<Int>
, Optional<Long>
, Optional<Double>
等替代。3. orElse方法的使用
3.3、orElse方法的使用
orElse 中調(diào)用的方法一直都會(huì)被執(zhí)行,orElseGet 方法只有在 Optional 對(duì)象不含值時(shí)才會(huì)被調(diào)用,所以使用 orElse 方法時(shí)需要謹(jǐn)慎, 以免誤執(zhí)行某些不被預(yù)期的操作。此種情況下,可使用 orElseGet 方法代替它。
總結(jié)
到此這篇關(guān)于Java新特性之Optional類超詳細(xì)介紹的文章就介紹到這了,更多相關(guān)Java新特性O(shè)ptional類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
servlet監(jiān)聽器的學(xué)習(xí)使用(三)
這篇文章主要為大家詳細(xì)介紹了servlet監(jiān)聽器學(xué)習(xí)使用的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Java對(duì)象數(shù)組的添加、刪除和遍歷代碼示例
在Java編程中,我們經(jīng)常需要對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行遍歷操作,并根據(jù)業(yè)務(wù)需求刪除部分元素,這篇文章主要給大家介紹了關(guān)于Java對(duì)象數(shù)組的添加、刪除和遍歷的相關(guān)資料,需要的朋友可以參考下2024-04-04Java如何將二維數(shù)組轉(zhuǎn)化為一維數(shù)組
這篇文章主要介紹了Java如何將二維數(shù)組轉(zhuǎn)化為一維數(shù)組,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08spring整合shiro框架的實(shí)現(xiàn)步驟記錄
Shiro是一個(gè)強(qiáng)大易用的Java安全框架,提供了認(rèn)證、授權(quán)、加密和會(huì)話管理等功能。下面這篇文章主要給大家介紹了關(guān)于spring整合shiro框架的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-05-05Java 在Excel中添加分離型餅圖、環(huán)形圖的方法
這篇文章主要介紹了Java 在Excel中添加分離型餅圖、環(huán)形圖的方法,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12Java非靜態(tài)成員變量之死循環(huán)(詳解)
下面小編就為大家?guī)硪黄狫ava非靜態(tài)成員變量之死循環(huán)(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09SpringCloud 服務(wù)注冊(cè)IP錯(cuò)誤的解決
這篇文章主要介紹了SpringCloud 服務(wù)注冊(cè)IP錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的思路案例詳解
本文通過兩個(gè)案例來介紹下Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的相關(guān)知識(shí),每種方法通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09Java虛擬機(jī)JVM之server模式與client模式的區(qū)別
這篇文章主要介紹了Java虛擬機(jī)JVM的client模式和Server模式兩者的區(qū)別和聯(lián)系2017-12-12