Java8-Lambda編程[3] Optional接口

引言

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)));
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 178,746評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,991評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,706評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 56,036評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 43,203評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,451評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,677評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,857評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 35,266評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,606評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,407評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,643評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,836評(píng)論 18 139
  • 引言 Stream意為流,是Lambda編程中的一個(gè)重要角色。Stream類主要用于對(duì)收集類、數(shù)組、文件的迭代,以...
    斯特的簡(jiǎn)書閱讀 463評(píng)論 0 0
  • Java8 in action 沒(méi)有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說(shuō)的函數(shù)式...
    鐵牛很鐵閱讀 1,257評(píng)論 1 2
  • Optional 本章內(nèi)容 如何為缺失的值建模 Optional 類 應(yīng)用Optional的幾種模式 使用Opti...
    追憶逝水年華閱讀 1,813評(píng)論 0 0
  • 本文獲得Stackify授權(quán)翻譯發(fā)表,轉(zhuǎn)載需要注明來(lái)自公眾號(hào)EAWorld。 作者:EUGEN PARASCHIV...
    72a1f772fe47閱讀 11,768評(píng)論 3 7