Java新特性之Optional類超詳細(xì)介紹
前言
Optional 類是 Java 8 才引入的,Optional 是個容器,它可以保存類型 T 的值,或者僅僅保存 null。Optional 提供了很多方法,這樣我們就不用顯式進(jìn)行空值檢測。Optional 類的引入很好的解決空指針異常。
Java 8 引入 Optional 類,用來解決 NullPointerException。 Optional 代替
if…else解決空指針問題,使代碼更加簡潔。
1、Optional類概述
1.1、Optional類介紹
Optional 類是 Java 8 才引入的,Optional 是個容器,它可以保存類型 T 的值,或者僅僅保存 null。Optional 提供了很多方法,這樣我們就不用顯式進(jìn)行空值檢測。Optional 類的引入很好的解決空指針異常。
Java 8 引入 Optional 類,用來解決 NullPointerException。 Optional 代替 if…else 解決空指針問題,使代碼更加簡潔。
1.2、Java8之前的空指針異常判斷
Java 在使用對象過程中,訪問任何方法或?qū)傩远伎赡軐?dǎo)致 NullPointerException:
例如我們通過以下方法,獲取存在 student 對象中的 Age 值。
public String getIsocode (Student student){
return student.getAge();
}在這樣的示例中,如果我們想要避免由 student 或 student.age 為空而導(dǎo)致的空指針問題,我們就需要采用防御式檢查減少 NullPointerException(在訪問每一個值之前對其進(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)闉榇藭喑龆鄠€不同的退出點(diǎn)(return),使得代碼維護(hù)變得艱難,之后每個可能的 null 檢查都會新增一個退出點(diǎn)。
為了簡化這個過程,我們來看看用 Optional 類是怎么做的。
1.3、Java8之后Optional的使用
當(dāng)需要判斷的量多時,此時的這些判斷語句可能會導(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 這個對象從數(shù)據(jù)庫中查出的
Student student = getStudent();
// 創(chuàng)建一個可接受 null 的 Optiona l類
Optional<Student> optional = Optional.ofNullable(student);
// 用法1:獲取 student 對象中的某個值,如果不存在的話則取默認(rèn)值(不具有短路作用)
Integer a1 = optional.map(Student::getAge).orElse(20));
// 用法2:獲取 student 對象中的某個值,如果不存在的話則取默認(rèn)值(具有短路作用,因?yàn)槭菓屑虞d)
Integer a2 = optional.map(Student::getAge).orElseGet(() -> Integer.MAX_VALUE);
// 用法3:判斷對象是否存在,不存在則拋出異常
optional.orElseThrow(() -> new RuntimeException("student不存在!"));
// 用法4:判斷對象是否存在,存在的話對對象進(jìn)行操作,例如給對象賦初始值
optional.ifPresent(o -> o.setAge(18));
// 用法5:對象存在時,且年齡滿足一定條件容器才會繼續(xù)保存這對象,否則將會剔除
optional.filter(o -> o.getAge() > 10);
}
}2、Optional類使用
2.1、Optional類常用方法總結(jié)
| 方法 | 描述 |
|---|---|
| empty | 返回一個空的 Optional 實(shí)例 |
| filter | 如果值存在并且滿足提供的謂詞,就返回包含該值的 Optional 對象;否則返回一個空的 Optional 對象 |
| flatMap | 如果值存在,就對該值執(zhí)行提供的 mapping 函數(shù)調(diào)用,返回一個 Optional 類型的值,否則就返回一個空的 Optional 對象 |
| get | 如果該值存在,將該值用 Optional 封裝返回,否則拋出一個 NoSuchElementException 異常 |
| ifPresent | 如果值存在,就執(zhí)行使用該值的方法調(diào)用,否則什么也不做 |
| isPresent | 如果值存在就返回 true,否則返回 false |
| map | 如果值存在,就對該值執(zhí)行提供的mapping 函數(shù)調(diào)用 |
| of | 將指定值用 Optional 封裝之后返回,如果該值為 null,則拋出一個 NullPointerException 異常 |
| ofNullable | 將指定值用 Optional 封裝之后返回,如果該值為 null,則返回一個空的 Optional 對象 |
| orElse | 如果有值則將其返回,否則返回一個默認(rèn)值 |
| orElseGet | 如果有值則將其返回,否則返回一個由指定的 Supplier 接口生成的值 |
| orElseThrow | 如果有值則將其返回,否則拋出一個由指定的 Supplier 接口生成的異常 |
2.2、Optional對象創(chuàng)建
2.2.1、Optional.empty()方法
使用 Optional.empty() 方法聲明一個空的 Optional:
// 通過靜態(tài)工廠方法 Optional.empty(),創(chuàng)建一個空的 Optional 對象 Optional<Student> optStudent = Optional.empty();
2.2.2、Optional.of(T t)方法
使用 Optional.of(T t) 方法創(chuàng)建一個包含非空值的 Optional 對象 (不推薦):
// 靜態(tài)工廠方法 Optional.of(T t),依據(jù)一個非空值創(chuàng)建一個 Optional 對象 Optional<Student> optStudent = Optional.of(student);
如果 student 為 null,這段代碼會立即拋出一個 NullPointerException,而不是等到訪問 student 的屬性值時才返回一個錯誤。
2.2.3、Optional.ofNullable(T t)方法
使用 Optional.ofNullable(T t) 方法創(chuàng)建一個包含可能為空的值的 Optional 對象 (推薦):
// 用靜態(tài)工廠方法 Optional.ofNullable(T t),你可以創(chuàng)建一個允許 null 值的 Optional 對象 Optional<Student> optStudent = Optional.ofNullable(student);
2.3、Optional對象獲取
2.3.1、get()方法
get() 方法,如果變量存在,它直接返回封裝的變量值,否則就拋出一個 NoSuchElementException 異常,不推薦使用:
optional.map(Student::getAge).get()
2.3.2、orElse(T other)方法
orElse(T other) 方法,它允許你在 Optional 對象不包含值時提供一個默認(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 對象不含值時才執(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 方法非常類似,它們遭遇 Optional 對象為空時都會拋出一個異常,但是使用 orElseThrow 可以定制希望拋出的異常類型:
optional.orElseThrow(() -> new RuntimeException("student不存在!"));2.3.5、ifPresent(Consumer<? super T> consumer)方法
ifPresent(Consumer<? super T> consumer) 方法,它讓能在變量值存在時執(zhí)行一個作為參數(shù)傳入的方法,否則就不進(jìn)行任何操作:
optional.ifPresent(o -> o.setAge(18));
2.4、Optional對象中值的提取和轉(zhuǎn)換
2.4.1、map()方法
map() 方法,如果值存在,就對該值執(zhí)行提供的 mapping 函數(shù)調(diào)用,如果值不存在,則返回一個空的 Optional 對象。
引入 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() 方法,對于嵌套式的 Optiona 結(jié)構(gòu),我們應(yīng)該使用 flatMap 方法,將兩層的 Optional 合并成一個。
我們試著重構(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 返回的是一個 Optional<Car> 類型的對象,這意味著 map 操作的結(jié)果是一個 Optional<Optional<Car>> 類型的對象。因此,它對 getInsurance 的調(diào)用是非法的。
下面應(yīng)用 map 和 flatMap 對上述示例進(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對象其他方法
2.5.1、isPresent()方法
可以使用 isPresent() 方法檢查 Optional 對象是否包含非空值,例如:
Optional<String> optional = Optional.of("Hello World");
if (optional.isPresent()) {
System.out.println(optional.get());
}2.5.2、filter()方法
filter() 方法接受一個謂詞作為參數(shù)。如果 Optional 對象的值存在,并且它符合謂詞的條件,filter 方法就返回其值,否則它就返回一個空的 Optional 對象。
比如,你可能需要檢查保險公司的名稱是否為 “Cambridge-Insurance”。
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
System.out.println("ok");
}使用 Optional 對象的 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è)計時就沒特別考慮將其作為類的字段使用,所以它也并未實(shí)現(xiàn) Serializable 接口。由于這個原因,如果你的應(yīng)用使用了某些要求序列化的庫或者框架,在域模型中使用Optional,有可能引發(fā)應(yīng)用程序故障。
然而,我們相信,通過前面的介紹,我們已經(jīng)看到用 Optional 聲明域模型中的某些類型是個不錯的主意,尤其是你需要遍歷有可能全部或部分為空,或者可能不存在的對象時。如果你一定要實(shí)現(xiàn)序列化的域模型,作為替代方案, 我們建議你像下面這個例子那樣,提供一個能訪問聲明為 Optional、變量值可能缺失的接口,代碼清單如下:
public class Person {
private Car car;
public Optional<Car> getCarAsOptional() {
return Optional.ofNullable(car);
}
}3.2、避免使用基礎(chǔ)類型的 Optional 對象
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)用的方法一直都會被執(zhí)行,orElseGet 方法只有在 Optional 對象不含值時才會被調(diào)用,所以使用 orElse 方法時需要謹(jǐn)慎, 以免誤執(zhí)行某些不被預(yù)期的操作。此種情況下,可使用 orElseGet 方法代替它。
總結(jié)
到此這篇關(guān)于Java新特性之Optional類超詳細(xì)介紹的文章就介紹到這了,更多相關(guān)Java新特性O(shè)ptional類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Swing程序設(shè)計實(shí)戰(zhàn)
今天教大家怎么用JavaSwing工具包實(shí)現(xiàn)一個程序的界面設(shè)計,文中有非常詳細(xì)的代碼示例及注釋,對正在學(xué)習(xí)Java的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05
Java數(shù)據(jù)結(jié)構(gòu)及算法實(shí)例:快速計算二進(jìn)制數(shù)中1的個數(shù)(Fast Bit Counting)
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)及算法實(shí)例:快速計算二進(jìn)制數(shù)中1的個數(shù)(Fast Bit Counting),本文直接給出實(shí)現(xiàn)代碼,代碼中包含詳細(xì)注釋,需要的朋友可以參考下2015-06-06
深入淺析Java中Static Class及靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類的不同
上次有朋友問我,java中的類可以是static嗎?我給他肯定的回答是可以的,在java中我們可以有靜態(tài)實(shí)例變量、靜態(tài)方法、靜態(tài)塊。當(dāng)然類也可以是靜態(tài)的,下面小編整理了些關(guān)于java中的static class相關(guān)資料分享在腳本之家平臺供大家參考2015-11-11
SpringBoot整合Milvus的實(shí)現(xiàn)
本文主要介紹了SpringBoot整合Milvus的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
一文教你掌握J(rèn)ava如何實(shí)現(xiàn)判空
實(shí)際項(xiàng)目中我們會有很多地方需要判空校驗(yàn),如果不做判空校驗(yàn)則可能產(chǎn)生NullPointerException異常。所以本文小編為大家整理了Java中幾個常見的判空方法,希望對大家有所幫助2023-04-04
Java?的訪問修飾符public,protected,private(封裝、繼承)
這篇文章主要介紹了Java?的訪問修飾符public,protected,private(封裝、繼承),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09
springboot使用之多個filter的執(zhí)行順序以及配置方式
這篇文章主要介紹了springboot使用之多個filter的執(zhí)行順序以及配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08

