??????Optional<T>
類(lèi)是一個(gè)容器類(lèi)谐岁,代表一個(gè)值存在不存在亥鬓。Optional<T>
用于避免和 null
檢查相關(guān)的 bug
湾笛。
創(chuàng)建Optional
-
Optional.empty()
—— 創(chuàng)建一個(gè)空的Optional對(duì)象Optional<String> optStr = Optional.empty();
-
Optional.of()
—— 依據(jù)非空值創(chuàng)建一個(gè)Optional
對(duì)象碳胳。如果試圖傳入一個(gè)null
值烦租,會(huì)馬上拋出一個(gè)NullPointerException
丸冕。Optional<String> optStr = Optional.of(str);
-
Optional.ofNullable()
—— 創(chuàng)建一個(gè)允許為null
值的Optional
對(duì)象耽梅。Optional<String> optStr = Optional.ofNullable(str);
map
和 flatMap
-
map
操作 —— 當(dāng)Optional
的值不為空時(shí),將Optional
的值轉(zhuǎn)換為對(duì)應(yīng)的值胖烛,并將其封裝成Optional
對(duì)象返回眼姐。如果原本的Optional
對(duì)象的值為空,則返回一個(gè)空的Optional
對(duì)象佩番。public <U> Optional<U> map(Function<? super T, ? extends U> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) { return empty(); } else { //拿到新值后众旗,再封裝成Optional類(lèi)型 return Optional.ofNullable(mapper.apply(value)); } }
-
flatMap
操作 —— 當(dāng)Optional
的值不為空時(shí),將Optional
的值轉(zhuǎn)換為對(duì)應(yīng)的值答捕,新的值必須為Optional
類(lèi)型逝钥,并將其直接返回。如果原本的Optional
對(duì)象的值為空拱镐,則返回一個(gè)空的Optional
對(duì)象艘款。public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) { return empty(); } else { @SuppressWarnings("unchecked") //拿到新值后強(qiáng)轉(zhuǎn)Optional類(lèi)型,不進(jìn)行封裝 Optional<U> r = (Optional<U>) mapper.apply(value); return Objects.requireNonNull(r); } }
注:
Optional
的 map
和 flatMap
如何選擇沃琅?
??????map
會(huì)將轉(zhuǎn)換好的值進(jìn)行一次Optional
包裝哗咆;flatMap
會(huì)確保轉(zhuǎn)換好的值為Optional
對(duì)象,然后直接返回益眉。使用 map
還是 flatMap
取決于 轉(zhuǎn)換好的值 是否是Optional
對(duì)象晌柬。
??????如果 轉(zhuǎn)換好的值 不是Optional
對(duì)象,使用map
,對(duì)其進(jìn)行再次包裝郭脂,以便執(zhí)行進(jìn)一步Optional
的操作年碘。
??????如果 轉(zhuǎn)換好的值 是 Optional
對(duì)象,使用flatMap
展鸡,直接返回屿衅。
Optional的其他行為
-
isPresent
方法:Optional
包含值的時(shí)候返回true
,否則返回false
。 -
ifPresent
方法:當(dāng)值存在時(shí)莹弊,使用該值執(zhí)行給定的代碼塊涤久,否則什么都不做涡尘。- 與Kotlin的安全調(diào)用運(yùn)算符相似,只有值不為空時(shí)响迂,具體方法才會(huì)被調(diào)用考抄。
-
get
方法: 如果值存在則將其返回,否則拋出一個(gè)NoSuchElement Exception
異常蔗彤。 -
orElse
方法:如果值存在則將其返回川梅,否則返回一個(gè)默認(rèn)值。 -
filter
方法:如果值存在幕与,并且滿足提供的謂詞挑势,就返回自身镇防;否則返回一個(gè)空的Optional
對(duì)象啦鸣。 -
orElseGet
方法:如果有值則將其返回,否則返回一個(gè)由指定的Supplier
接口生成的值来氧。(Supplier
方法只有在Optional
不為空時(shí)才執(zhí)行調(diào)用) -
orElseThrow
方法:如果有值則將其返回诫给,否則返回一個(gè)由指定的Supplier
接口生成的異常。 -
orElseThrow
重載方法(無(wú)參):如果有值則將其返回啦扬,否則直接拋出NoSuchElementException
中狂。(Java 10) -
or
方法:如果值存在則將其返回,否則返回由指定的Supplier
接口生成的另一個(gè)Optional
對(duì)象扑毡。(Java 9) -
ifPresentOrElse
方法:如果值存在胃榕,則使用該值作為參數(shù),執(zhí)行指定的Consumer接口瞄摊;如果該值不存在勋又,則執(zhí)行給定的Runnable,處理值為空的情況换帜。(Java 9)
Optional
與序列化
??????對(duì)于值可能缺失(即可能為null)的屬性楔壤,可以將其使用Optional
包裹,明確表示該屬性的值可缺失惯驼,類(lèi)似kotlin
的可空類(lèi)型蹲嚣,強(qiáng)制需要進(jìn)行空檢查。
public class Person{
//Car可能為null,使用Optional對(duì)其進(jìn)行封裝
private Optional<Car> car;
public Optional<Car> getCar() {return car; }
}
??????但由于Optional
的設(shè)計(jì)初衷僅僅是要支持能返回Optional
對(duì)象的語(yǔ)法祟牲,因此它沒(méi)實(shí)現(xiàn) Serializable
接口隙畜。如果使用某些要求序列化的庫(kù)或框架,在域模型中使用 Optional
说贝,有可能引發(fā)程序故障议惰。
??????如果一定要實(shí)現(xiàn)序列化的域模型,替代方案是:提供一個(gè)能訪問(wèn)聲明為Optional
狂丝、變量值可能缺失的接口:
public class Person{
//雖然知道car可空换淆,但不提倡一開(kāi)始就定義Optional<Car>類(lèi)型哗总。
private Car car;
public Optional<Car> getCarAsOptional(){
return Optional.ofNullable(car);
}
}
Optional 與 流
?????? JAVA 9 引入了 Optional
的 stream()
方法,該方法可以把一個(gè)含值的 Optional
對(duì)象轉(zhuǎn)換成由該值構(gòu)成的Stream
對(duì)象倍试,或者把一個(gè)空的 Optional
對(duì)象轉(zhuǎn)換成等價(jià)的空Stream
讯屈。該方法為處理由 Optional
構(gòu)成的 Stream
提供極大的便利。
//Optional#stream源碼
public Stream<T> stream() {
if (!isPresent()) {
return Stream.empty();
} else {
return Stream.of(value);
}
}
借助Optional
,可以安全的對(duì)流元素進(jìn)行轉(zhuǎn)換县习、刷選的操作:
//依據(jù)前面序列化的要求涮母,提供返回Optional封裝屬性的方法。
class Person{
private Car car;
public Optional<Car> getCarAsOptional() { return Optional.ofNullable(car); }
}
class Car{
private Insurance insurance;
public Optional<Insurance> getInsuranceAsOptional() { return Optional.ofNullable(insurance); }
}
class Insurance{
private String name;
public String getName() { return name; }
}
//接收一個(gè)Person列表
public Set<String> getCarInsuranceNames(List<Person> persons){
return persons.stream()
//將流轉(zhuǎn)換為Stream<Optional<Car>>
.map(Person::getCarAsOptional)
//將流轉(zhuǎn)換為Stream<Optional<Insurance>>
.map(optCar -> optCar.flatMap(Car::getInsuranceAsOptional))
//將流轉(zhuǎn)換為Stream<Optional<String>>
.map(optIns -> optIns.map(Insurance::getName))
//該流轉(zhuǎn)換成Stream<String>
//如果使用Java8的躁愿,也可以參考Optional#stream進(jìn)行處理
.flatMap(Optional::stream)
//將結(jié)果收集成Set
.collect(Collectors.toSet());
}
?????? 在 JDK 1.8 的環(huán)境下叛本,可模仿Optional#stream()
寫(xiě)一個(gè)將Optional
對(duì)象轉(zhuǎn)換為Stream
對(duì)象的靜態(tài)方法,然后使用方法引用將替代Optional::stream
即可彤钟。這里可以使用OptionalUtility::stream
替代 Optional::stream
来候。
//OptionalUtility.java
public static <T> Stream<T> stream(Optional<T> optional) {
if (!optional.isPresent()) {
return Stream.empty();
} else {
return Stream.of(optional.get());
}
}
??????對(duì)于早已定義好,不提供一個(gè)能訪問(wèn)聲明為Optional
逸雹、變量值可能缺失的接口的域模型营搅,可以手動(dòng)將流的元素轉(zhuǎn)換為Optional
對(duì)象:
class Person{
private Car car;
public Car getCar() {return car;}
}
class Car{
private Insurance insurance;
public Insurance getInsurance() { return insurance;}
}
class Insurance{
private String name;
public String getName() { return name; }
}
persons.stream()
//對(duì)元素使用Optional包裝
.map(Optional::ofNullable)
.map(optPer -> optPer.map(Person::getCar))
.map(optCar -> optCar.map(Car::getInsurance))
.map(optIns -> optIns.map(Insurance::getName))
//如果使用Java8的,也可以參考Optional#stream進(jìn)行處理
//拆除Optional包裝
.flatMap(Optional::stream)
.collect(Collectors.toSet());
異常與Optional
??????由于某些原因梆砸,函數(shù)無(wú)法返回某個(gè)值转质,除了返回null
外,Java API
比較常見(jiàn)的替代做法是拋出一個(gè)異常(最典型的例子就是Interger.parseInt()
)帖世。我們可以使用空的 Optional
對(duì)象休蟹,對(duì)遭遇無(wú)法轉(zhuǎn)換的String進(jìn)行建模時(shí)返回的非法值進(jìn)行建模,從而不需要再封裝try/catch :
//OptionalUtility.java
public static Optional<Integer> stringToInt(String s){
try{
return Optional.ofNullable(Integer.parseInt(s));
}catch (Exception e){
return Optional.empty();
}
}
//
HashMap<String,String> map = new HashMap<>();
map.put("Java","8");
int version = Optional.ofNullable(map.get("Java"))
//使用OptionalUtility#stringToInt進(jìn)行轉(zhuǎn)換日矫。
.flatMap(OptionalUtility::stringToInt)
.orElse(0);
以不解包的方式組合兩個(gè)Optional對(duì)象
??????當(dāng)需要操作兩個(gè)Optional
對(duì)象進(jìn)行運(yùn)算并返回包含結(jié)果的Optional
對(duì)象時(shí)赂弓,或許你會(huì)想到以下實(shí)現(xiàn)方法:
public Insurance findInsurance(Person person,Car car){
//經(jīng)過(guò)運(yùn)算得到正確的Insurance
//此處模擬返回一個(gè)Insurance對(duì)象
return insurance;
}
public Optional<Insurance> nullSafeFindInsurance(Optional<Person> person,Optional<Car> car){
//判斷兩個(gè)Optional的值都存在
if (person.isPresent() && car.isPresent()){
//只有兩個(gè)Optional的值都存在,才取出進(jìn)行運(yùn)算搬男。
return Optional.ofNullable(findInsurance(person.get(),car.get()));
}else {
//否則返回一個(gè)空Optional對(duì)象拣展。
return Optional.empty();
}
}
??????但這樣的代碼跟我們手動(dòng)判空區(qū)別不大,但其實(shí)可以結(jié)合 flatMap
和 map
缔逛,不解包的方式下實(shí)現(xiàn):
public Optional<Insurance> nullSafeFindInsurance(Optional<Person> person,Optional<Car> car){
//如果person的值不存在备埃,則直接返回一個(gè)空Optional對(duì)象。
//至于為什么用flatMap褐奴,因?yàn)閏ar.map返回的是一個(gè)Optional對(duì)象
return person.flatMap(p ->
//如果map的值不存在按脚,則直接返回一個(gè)空Optional對(duì)象。
car.map(c ->
//執(zhí)行到這一步敦冬,說(shuō)明兩個(gè)Optional的值都存在辅搬,利用這兩個(gè)值進(jìn)行運(yùn)算。
//運(yùn)算出的Insurance值,map函數(shù)會(huì)對(duì)其使用Optional進(jìn)行封裝堪遂。
findInsurance(p,c))
);
}
簡(jiǎn)化if-else
??????當(dāng)需要對(duì)某一個(gè)變量深度獲取值時(shí)介蛉,往往會(huì)伴隨多次判空,使用 Optional
能優(yōu)化 if-else
結(jié)構(gòu):
public String getInsuranceByPerson(Person person){
return Optional.ofNullable(person)
.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName)
.orElse("daqi");
}
總結(jié)
?????? Optional
類(lèi)有時(shí)候并沒(méi)有讓代碼變得更簡(jiǎn)潔溶褪,他的作用更多的是把 類(lèi)型 轉(zhuǎn)換為 對(duì)應(yīng)的"可空類(lèi)型" 币旧,強(qiáng)制進(jìn)行空檢查(與Kotlin定義可空類(lèi)型相似)。
?????? 總體來(lái)說(shuō)猿妈,Optional
可確保 流 進(jìn)行map
吹菱、filter
操作時(shí)的空安全,以及對(duì)某個(gè)變量進(jìn)行深度取值時(shí)簡(jiǎn)化if-else
流程和確迸碓颍空安全鳍刷。當(dāng)然也可以把普通的空檢查轉(zhuǎn)換為 Optional
后,再進(jìn)行操作俯抖。
參考資料
Java8系列
Java 8 知識(shí)歸納(一)—— 流 與 Lambda
Java 8 知識(shí)歸納(二)—— Optional
Java 8 知識(shí)歸納(三)—— 日期API