引言
Optional意為可選,我們前面已經(jīng)提及過(guò),主要是為了替代null的使用,避免空指針異常(NullPointerException)的出現(xiàn)。譬如定義下面一個(gè)類A:
class A {
private String name;
public String getName() {
return name;
}
如果我調(diào)用它的getName方法獲取name字段并進(jìn)行后續(xù)操作,就將會(huì)發(fā)生異常。因?yàn)槲也](méi)有為A寫一個(gè)構(gòu)造方法,所以name字段將會(huì)為空,如果此時(shí)對(duì)結(jié)果進(jìn)行操作,比如調(diào)用length方法,將會(huì)拋出一個(gè)空指針異常,這就會(huì)很惱人。在沒(méi)有Optional接口之前,我們常用的避免異常發(fā)生的方法是添加一個(gè)硬性檢查。
例4.0:
String name=new A().getName;
if(name!=null)
System.out.println(name.length());
空城流云 Optional接口
上述代碼并不是一個(gè)美觀的寫法,因?yàn)榭偸且趫?zhí)行命令前進(jìn)行檢查,就好像套了一個(gè)try-catch塊一樣冗余丑陋。為了可以消除這樣的樣板代碼,J8提供了Optional接口,利用它可以寫出函數(shù)式風(fēng)格的代碼來(lái),比如下面的變形。
例4.1:
Optional<A> oa=Optional.ofNullable(new A());
oa.map(A::getName)
.map(String::length)
.ifPresent(System.out::println);
這樣的寫法看起來(lái)就好像是我們最先學(xué)過(guò)的Stream類的風(fēng)格,連方法名都很相似。我們一個(gè)一個(gè)來(lái)分析,最上面的ofNullable工廠方法負(fù)責(zé)生產(chǎn)一個(gè)Optional<A>類型的對(duì)象,這樣的方法一共有三個(gè),除了ofNullable還有of、和empty。of方法接受一個(gè)A類型的參數(shù),并將其裝箱成Optional<A>類型的對(duì)象,如果傳進(jìn)來(lái)的參數(shù)為空,則會(huì)直接拋出一個(gè)NullPointerException,確保被裝箱的對(duì)象非空(這里我姑且亂用“裝箱”和“拆箱”這兩個(gè)術(shù)語(yǔ),因?yàn)镺ptional對(duì)象很像是把一個(gè)對(duì)象放到了箱子里,當(dāng)然它也可能會(huì)是一個(gè)空箱子)。empty則正好相反,調(diào)用后直接生成一個(gè)內(nèi)容為空的Optional對(duì)象,這使得該對(duì)象的功能有點(diǎn)像null,但實(shí)際上差別很大,要不然Optional類還有什么意義呢?最后是我們上面使用的ofNullable方法,顧名思義,就是可以傳進(jìn)來(lái)一個(gè)可空的A類型參數(shù),結(jié)合了上面兩個(gè)方法的功能,相比于of方法,此方法在傳入值為null為參數(shù)時(shí)并不會(huì)拋出異常,而是直接生成一個(gè)空內(nèi)容的Optional<A>對(duì)象。
生成Optional后,我們又繼續(xù)調(diào)用了一個(gè)為map的方法,它與Stream中的map方法很相似,都是對(duì)泛型對(duì)象進(jìn)行映射。上例中我們按照A::getName方法所對(duì)應(yīng)的映射規(guī)則,將一個(gè)Optional<A>類型的對(duì)象映射成了一個(gè)Optional<String>類型的對(duì)象,緊接著又將其映射成了一個(gè)Optional<Integer>類型的對(duì)象。除了map外,Optional還有filter和flatmap兩個(gè)方法與Stream很相似。filter很好理解,傳入一個(gè)Predicate類型的參數(shù)來(lái)對(duì)Optional對(duì)象進(jìn)行所謂的篩選,如果符合條件就保持不變返回對(duì)象本身,否則將會(huì)返回一個(gè)空內(nèi)容的Optional。flatMap也很好理解,在Stream中它負(fù)責(zé)將映射后的對(duì)象整合成一個(gè)流,而在這里,它的作用是將Optional對(duì)象拆成一層包裝的形式,比如對(duì)于下面這個(gè)類:
class B {
private A a;
public Optional<A> getA() {
return Optional.of(a);
}
如果我們想要獲取new B().getA().getName(),直接調(diào)用兩次map方法是不行的。因?yàn)槲覀優(yōu)榱朔乐钩蓡Ta為null,進(jìn)而導(dǎo)致空指針異常,使用了Optional<A>類型的返回值。因此直接對(duì)Optional<B>對(duì)象按照getA方法進(jìn)行映射操作,會(huì)得到一個(gè)Option<Option<A>>類型的對(duì)象,拆箱后的結(jié)果是一個(gè)Optional<A>而不是A,所以無(wú)法按照getName方法進(jìn)行映射。為此我們需要使用flatMap,此方法會(huì)自動(dòng)進(jìn)行拆箱,得到一個(gè)只有一層包裝的Optional<A>對(duì)象。譬如下面的代碼,通過(guò)flatMap及后續(xù)方法,獲取含有Name為字母“a”開(kāi)頭字符串的A字段的B對(duì)象。
例4.2:
String s = Optional.ofNullable(new B())
.flatMap(B::getA)
.map(A::getName)
.filter(str->str.startsWith("a"))
.get();
這里我們先通過(guò)flatMap方法獲取了Optional<A>對(duì)象,在通過(guò)map方法獲取了Optional<String>對(duì)象,然后通過(guò)filter方法進(jìn)行判斷,如果箱內(nèi)的字符串不以字母“a”開(kāi)頭,則返回一個(gè)空箱。上述代碼的最后我們調(diào)用了一個(gè)get方法,這是Optional類最常用的一個(gè)方法,直接獲取箱內(nèi)的值,如果是空箱,則會(huì)返回null。如果我們想要對(duì)s進(jìn)行標(biāo)準(zhǔn)輸出,那么我們會(huì)收到一條無(wú)此元素異常(NoSuchElementException)而不是空指針異常,不過(guò)是換了個(gè)異常名字,這使得Optional類看起來(lái)好像沒(méi)毛用,為了避免異常我們還是要進(jìn)行“!=null”形式的判斷,不過(guò)這次可以直接用Optional的方法來(lái)執(zhí)行。
例4.3:
Optional<String> os = Optional.ofNullable(new B())
.flatMap(B::getA)
.map(A::getName)
.filter(str->str.startsWith("a"))
.ifPresent(System.out::println);
上述代碼中最后調(diào)用的就是我們一直留著沒(méi)講的ifPresent方法,這個(gè)方法看名字也很好理解,它傳入一個(gè)Consumer類型的參數(shù),如果Optional對(duì)象存在內(nèi)容,則消費(fèi)里面的對(duì)象,即對(duì)其執(zhí)行Consumer中對(duì)應(yīng)的操作。此外還有一個(gè)類似的isPresent方法,用于判斷箱內(nèi)是否為空。
Schr?dinger's null
有些人會(huì)類比于Stream的兩類方法,將map、flatMap、filter歸為一類,get、ifPresent、isPresent歸為一類,因?yàn)榍罢叻祷氐氖荗ptional類型的對(duì)象,可以進(jìn)行級(jí)聯(lián),而后者的返回值則是其他類型,只能用在級(jí)聯(lián)末尾。他們確實(shí)都很相似,包括上一章的Collector與后面要講的CompleteFuture在內(nèi),在設(shè)計(jì)上都是想要體現(xiàn)一種Java8獨(dú)特的函數(shù)式編程風(fēng)格。
get方法很像Stream中的collect方法,會(huì)將Stream<T>中的元素收集為T收集或T數(shù)組,get則會(huì)將Optinal<T>轉(zhuǎn)化為T對(duì)象,前提是Optinal對(duì)象內(nèi)部的T對(duì)象不為null。那么一旦T對(duì)象為null會(huì)怎樣呢,前面我們已經(jīng)說(shuō)過(guò)get方法會(huì)拋出無(wú)此元素異常,這讓Optional類顯得很雞肋。實(shí)際上get方法還有幾個(gè)兄弟,我一直藏著沒(méi)講,它們的名字叫做orElse、orElseGet、orElseThrow。orElse方法傳入一個(gè)T類型的參數(shù),當(dāng)內(nèi)容為空時(shí)返回該參數(shù)作為缺省值,orElseGet則傳入一個(gè)Supplier對(duì)象來(lái)提供缺省值。orElseThrow則傳入一個(gè)Supplier用來(lái)生成要拋出的異常,有了它我們對(duì)于空值就可以不再滿足于默認(rèn)的空指針異常以及get方法提供的無(wú)此元素異常,而可以定制自己的異常,這聽(tīng)起來(lái)是不是很瘋狂呢?畢竟有時(shí)候我們想要體驗(yàn)J8的新功能,又需要拋出定制異常的獲取錯(cuò)誤信息,那么就可以把二者結(jié)合起來(lái),使用Optional的orElseThrow方法來(lái)使代碼看起來(lái)更加有格調(diào)。
大試牛刀
學(xué)習(xí)了Optional接口所有的方法,我們就來(lái)實(shí)際運(yùn)用一下,對(duì)下面的代碼進(jìn)行一次變形。直接在第一行就寫return會(huì)給人帶來(lái)非常爽快的感覺(jué),下面的代碼看起來(lái)就像是函數(shù)式編程語(yǔ)言中的閉包。
例4.4:
//命令式
public String getFirst(String s) {
if (s != null)
if (s.length() != 0)
return s.substring(0, 1);
return "空";
}
//函數(shù)式
public String _getFirst(String s){
return Optional.ofNullable(s)
.filter(str->str.length()!=0)
.map(str->str.substring(0,1))
.orElse("空");
}
小結(jié) 可選的Optional
說(shuō)了這么多,可能還是有很多人想問(wèn),這個(gè)Optional到底有什么用呢,好像很雞肋不用也罷。就好比get方法,和我直接進(jìn)行非空檢查相比能簡(jiǎn)潔到哪里去呢,還要在外面在套一層Optional殼,使代碼看起來(lái)更加繁瑣更加費(fèi)解,而且不斷地拆箱裝箱也很煩人,還不如用我以前學(xué)過(guò)的習(xí)慣性寫法。這個(gè)東西其實(shí)見(jiàn)仁見(jiàn)智,就好比匿名內(nèi)部類與Lambda表達(dá)式孰優(yōu)孰劣,不同人有著不同的看法。函數(shù)式風(fēng)格的代碼其實(shí)是一種思想,或者在這里也可以看成是一種設(shè)計(jì)模式,比起以前那種冗長(zhǎng)的命令式代碼,函數(shù)式代碼看起來(lái)更加清晰、玄妙。至于這種風(fēng)格是否能夠長(zhǎng)遠(yuǎn)的發(fā)展下去,甚至廣泛取代傳統(tǒng)寫法,歷史自有定論,我等只需將這些新思想傳播開(kāi)來(lái),但并不會(huì)強(qiáng)制要求所有人都要接受,用與不用還是在于程序員自身的偏好。
最后讓我們?cè)賮?lái)看一串代碼,將兩個(gè)Optional拆箱運(yùn)算后再返回一個(gè)Optional對(duì)象,這段代碼我就不講解了,有興趣的讀者可以玩味一番。
例4.5:
public Optional,String> concat(Optional<String>s1,Optional<String>s2){
return s1.flatMap(str1->s2.map(str2->str1.concat(str2)));
}