Java8 Optional

一、簡介

到目前為止贮缅,臭名昭著的空指針異常是導(dǎo)致Java應(yīng)用程序失敗的最常見原因。以前介却,為了解決空指針異常谴供,Google公司著名的Guava項(xiàng)目引入了Optional類,Guava通過使用檢查空值的方式來防止代碼污染齿坷,它鼓勵(lì)程序員寫更干凈的代碼桂肌。受到Google Guava的啟發(fā),Optional類已經(jīng)成為Java 8類庫的一部分永淌。

Optional實(shí)際上是個(gè)容器:它可以保存類型T的值崎场,或者僅僅保存null。Optional提供很多有用的方法遂蛀,這樣我們就不用顯式進(jìn)行空值檢測谭跨。

此處引用java 8實(shí)戰(zhàn)里面的描述:

在你的代碼中始終如一地使用Optional,能非常清晰地界定出變量值的缺失是結(jié)構(gòu)上的問題李滴,還是你算法上的缺陷螃宙,抑或是你數(shù)據(jù)中的問題。另外所坯,我們還想特別強(qiáng)調(diào)谆扎,引入Optional類的意圖并非要消除每一個(gè)null引用。與此相反芹助,它的目標(biāo)是幫助你更好地設(shè)計(jì)出普適的API燕酷,讓程序員看到方法簽名籍凝,就能了解它是否接受一個(gè)Optional的值。這種強(qiáng)制會(huì)讓你更積極地將變量從Optional中解包出來苗缩,直面缺失的變量值。

所以声诸,optional的作用是顯示的表達(dá)變量的狀態(tài)酱讶,而不是為了完全消滅NPE。

二彼乌、是否使用Optional對比

2.1 不使用Optional
/**
 * 常規(guī)實(shí)現(xiàn):根據(jù)客戶信息獲取車輛保險(xiǎn)公司的名稱
 *
 * @param person
 * @return
 */
public static String getCarInsuranceName(Person person) {
    //對客戶判空
    if (person == null) {
        return "Unknown";
    }
    //對車輛判空
    Car car = person.getCar();
    if (car == null) {
        return "Unknown";
    }
    //對保險(xiǎn)公司判空
    Insurance insurance = car.getInsurance();
    if (insurance == null) {
        return "Unknown";
    }
    return insurance.getName();
}

以上代碼泻肯,可能是我日常開發(fā)經(jīng)常遇到的,過多的NullPointerException檢查慰照,影響到了代碼的可讀性以及優(yōu)雅性灶挟,而且存在潛在的風(fēng)險(xiǎn),假設(shè)其中某個(gè)環(huán)節(jié)未做空指針的檢查毒租,則會(huì)看到我們熟悉而又討厭的NullPointerException稚铣。

2.2 使用Optional
/**
 * Optional實(shí)現(xiàn):根據(jù)客戶信息獲取車輛保險(xiǎn)公司的名稱
 *
 * @param person
 * @return
 */
public static String getCarInsuranceName2(Person person) {
    //當(dāng)Optional對象為空時(shí),則后面的map都不會(huì)繼續(xù)執(zhí)行
    return Optional.ofNullable(person)
            .map(Person::getCar)
            .map(Car::getInsurance)
            .map(Insurance::getName)
            .orElse("Unknown");
}
2.3 測試
public static void main(String[] args) {
    //測試1
    Person person = new Person();
    System.out.println(getCarInsuranceName(person));
    System.out.println(getCarInsuranceName2(person));
    System.out.println("--------------------------");

    //測試2
    Insurance insurance = new Insurance("xx保險(xiǎn)公司");
    Car car = new Car(insurance);
    Person person2 = new Person(car);
    System.out.println(getCarInsuranceName(person2));
    System.out.println(getCarInsuranceName2(person2));
}

運(yùn)行結(jié)果

Unknown
Unknown
--------------------------
xx保險(xiǎn)公司
xx保險(xiǎn)公司

三墅垮、Optional的構(gòu)造方式

為了控制生成實(shí)例的方式惕医,也是為了收緊空值Optional的定義,Optional將構(gòu)造函數(shù)定義為private算色。想要?jiǎng)?chuàng)建Optional實(shí)例抬伺,可以借助of和ofNullable兩個(gè)方法實(shí)現(xiàn)。這兩個(gè)方法的區(qū)別在于:of方法傳入的參數(shù)不能是null的灾梦,否則會(huì)拋出NullPointerException峡钓。所以,對于可能是null的結(jié)果若河,一定使用ofNullable能岩。

Optional類中還有一個(gè)靜態(tài)方法:empty,這個(gè)方法直接返回了內(nèi)部定義的一個(gè)常量Optional<?> EMPTY = new Optional<>()牡肉,這個(gè)常量的value是null捧灰。ofNullable方法也是借助了empty實(shí)現(xiàn)null的包裝。所以說统锤,對于null的Optional包裝類毛俏,指向的都是相同的實(shí)例對象,Optional.empty() == Optional.ofNullable(null)返回的是true饲窿。換句話說煌寇,空Optional是單例的。

3.1 Optional.of(T)

該方式的入?yún)⒉荒転閚ull逾雄,否則會(huì)有NPE阀溶,在確定入?yún)⒉粸榭諘r(shí)使用該方式腻脏。

3.2 Optional.ofNullable(T)

該方式的入?yún)⒖梢詾閚ull,當(dāng)入?yún)⒉淮_定為非null時(shí)使用银锻。

3.3 Optional.empty()

這種方式是返回一個(gè)空Optional永品,等效Optional.ofNullable(null)

四、常用函數(shù)

4.1 of

為非null的值創(chuàng)建一個(gè)Optional击纬。of方法通過工廠方法創(chuàng)建Optional類鼎姐。需要注意的是,創(chuàng)建對象時(shí)傳入的參數(shù)不能為null更振。如果傳入?yún)?shù)為null炕桨,則拋出NullPointerException。因此不經(jīng)常用肯腕。

Optional<Person> personOpt = Optional.of(person);
4.2 ofNullable

為指定的值創(chuàng)建一個(gè)Optional献宫,如果指定的值為null,則返回一個(gè)空的Optional实撒。

Optional<Person> personOpt = Optional.ofNullable(person);
4.3 isPresent

如果值存在返回true姊途,否則返回false。

public static String getGender(Student student){
    Optional<Student> stuOpt =  Optional.ofNullable(student);
    if(stuOpt.isPresent())
    {
        return stuOpt.get().getGender();
    }

    return "Unkown";
}

這種用法不但沒有減少null的防御性檢查奈惑,而且增加了Optional包裝的過程吭净,違背了Optional設(shè)計(jì)的初衷,因此開發(fā)中要避免這種糟糕的使用肴甸。

4.4 ifPresent

如果Optional實(shí)例有值則為其調(diào)用consumer寂殉,否則不做處理。

public static void printName(Student student){
    Optional.ofNullable(student).ifPresent(u ->  System.out.println("The student name is : " + u.getName()));
}
4.5 get

如果Optional有值則將其返回原在,否則拋出NoSuchElementException友扰。因此也不經(jīng)常用。

4.6 orElse

如果有值則將其返回庶柿,否則返回指定的其它值村怪。

public static String getName(Person person) {
    return Optional.ofNullable(person).map(Person::getName).orElse("Unknown");
}
4.7 orElseGet

orElseGet與orElse方法類似,區(qū)別在于得到的默認(rèn)值浮庐。orElse方法傳入的參數(shù)是明確的默認(rèn)值甚负,orElseGet方法傳入的參數(shù)是獲取默認(rèn)值的函數(shù)。

如果默認(rèn)值的構(gòu)造過程比較復(fù)雜审残,需要經(jīng)過一系列的運(yùn)算邏輯梭域,那一定要使用orElseGet,因?yàn)閛rElseGet是在值為空的時(shí)候搅轿,才會(huì)執(zhí)行函數(shù)病涨,并返回默認(rèn)值,如果值不為空璧坟,則不會(huì)執(zhí)行函數(shù)既穆,相比于orElse而言赎懦,減少了一次構(gòu)造默認(rèn)值的過程。

Optional<Wallet> findByAccountId(Integer accountId);

//為null時(shí)幻工,創(chuàng)建一下新的
Wallet wallet = repository.findByAccountId(accountId).orElseGet(() -> {
           Wallet newWallet = new Wallet();
           Account account = new Account();
           account.setId(accountId);
           newWallet.setMoney(0D);
           newWallet.setAccount(account);
           repository.save(newWallet);
           return newWallet;
       });
4.8 orElseThrow

如果有值則將其返回励两,否則拋出supplier接口創(chuàng)建的異常。

Account account = Optional.ofNullable(databaseUserRepo.findByUsername(username)).orElseThrow(() -> new UsernameNotFoundException("用戶" + username + "不存在"))

4.9 filter

如果有值并且滿足斷言條件返回包含該值的Optional会钝,否則返回空Optional伐蒋。

public static void filterAge(Student student){
    Optional.ofNullable(student).filter( u -> u.getAge() > 18).ifPresent(u ->  System.out.println("The student age is more than 18."));
}
4.10 map

如果有值,則對其執(zhí)行調(diào)用mapping函數(shù)得到返回值迁酸。如果返回值不為null,則創(chuàng)建包含mapping返回值的Optional作為map方法返回值俭正,否則返回空Optional奸鬓。

map()方法的源碼:

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
   Objects.requireNonNull(mapper);
   if (!isPresent())
       return empty();
   else {
       return Optional.ofNullable(mapper.apply(value));
   }
}

map()方法的參數(shù)為Function(函數(shù)式接口)對象,map()方法將Optional中的包裝對象用Function函數(shù)進(jìn)行運(yùn)算掸读,并包裝成新的Optional對象(包裝對象的類型可能改變)串远。舉例如下:

public static String getCarInsuranceName2(Person person) {
    //當(dāng)Optional對象為空時(shí),則后面的map都不會(huì)繼續(xù)執(zhí)行
    return Optional.ofNullable(person)
            .map(Person::getCar)
            .map(Car::getInsurance)
            .map(Insurance::getName)
            .orElse("Unknown");
}
4.11 flatMap

如果有值儿惫,為其執(zhí)行mapping函數(shù)返回Optional類型返回值澡罚,否則返回空Optional。

flatMap()方法的源碼:

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value));
    }
}

跟map()方法不同的是肾请,入?yún)unction函數(shù)的返回值類型為Optional<U>類型留搔,而不是U類型,這樣flatMap()能將一個(gè)二維的Optional對象映射成一個(gè)一維的對象铛铁,faltMap()改寫如下:

public static String getCarInsuranceName3(Person person) {
    //當(dāng)Optional對象為空時(shí)隔显,則后面的flatMap都不會(huì)繼續(xù)執(zhí)行
    return Optional.ofNullable(person)
            .flatMap(e -> Optional.ofNullable(e.getCar()))
            .flatMap(c -> Optional.ofNullable(c.getInsurance()))
            .map(Insurance::getName)
            .orElse("Unknown");
}

map和flatMap是對Optional的值進(jìn)行操作的方法,區(qū)別在于饵逐,map會(huì)將結(jié)果包裝到Optional中返回括眠,flatMap不會(huì)。但是兩個(gè)方法返回值都是Optional類型倍权,這也就要求掷豺,flatMap的方法函數(shù)返回值需要是Optional類型。

4.12 其他:equals薄声、hashCode当船、toString

Optional重寫了這三個(gè)方法。因?yàn)镺ptional可以認(rèn)為是包裝類奸柬,所以還是圍繞這被包裝的值重寫這三個(gè)方法生年。

  • equals方法,Optional.of(s1).equals(Optional.of(s2))完全等價(jià)于s1.equals(s2)廓奕。

  • hashCode方法抱婉,直接返回的是值的hashCode档叔,如果是空Optional,返回的是0蒸绩。

  • toString方法衙四,為了能夠識別是Optional,將打印數(shù)據(jù)包裝了一下患亿。如果是空Optional传蹈,返回的是字符串“Optional.empty”;如果是非空步藕,返回是是“Optional[值的toString]”惦界。

五、正確使用Optional

5.1 盡量避免使用的地方

1咙冗、避免使用Optional.isPresent()來檢查實(shí)例是否存在沾歪,因?yàn)檫@種方式和null != obj沒有區(qū)別,這樣用就沒什么意義了雾消。

2灾搏、避免使用Optional.get()方式來獲取實(shí)例對象,因?yàn)槭褂们靶枰褂肙ptional.isPresent()來檢查實(shí)例是否存在立润,否則會(huì)出現(xiàn)NPE問題狂窑。

3、避免使用Optional作為類或者實(shí)例的屬性桑腮,而應(yīng)該在返回值中用來包裝返回實(shí)例對象泉哈。

4、避免使用Optional作為方法的參數(shù)到旦,原因同3旨巷。

5.2 示例

使用Optional,我們就可以把下面這樣的代碼進(jìn)行改寫

public static String getName(Person person) {
    if (person == null) {
        return "Unknown";
    }
    return person.getName();
}

不過添忘,千萬不要改寫成這副樣子

public static String getName(Person person) {
    Optional<Person> personOpt = Optional.ofNullable(person);
    if (!personOpt.isPresent()) {
        return "Unknown";
    }
    return person.getName();
}

這樣改寫非但不簡潔采呐,而且其操作還是和第一段代碼一樣。無非就是用isPresent方法來替代u==null搁骑。這樣的改寫并不是Optional正確的用法斧吐,我們再來改寫一次。

public static String getName(Person person) {
    return Optional.ofNullable(person).map(Person::getName).orElse("Unknown");
}

這樣才是正確使用Optional的姿勢仲器。那么按照這種思路煤率,我們可以安心的進(jìn)行鏈?zhǔn)秸{(diào)用,而不是一層層判斷了乏冀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蝶糯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辆沦,更是在濱河造成了極大的恐慌昼捍,老刑警劉巖识虚,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妒茬,居然都是意外死亡担锤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門乍钻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肛循,“玉大人,你說我怎么就攤上這事银择《嗫罚” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵浩考,是天一觀的道長熬丧。 經(jīng)常有香客問我,道長怀挠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任害捕,我火速辦了婚禮绿淋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尝盼。我一直安慰自己吞滞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布盾沫。 她就那樣靜靜地躺著裁赠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赴精。 梳的紋絲不亂的頭發(fā)上佩捞,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機(jī)與錄音蕾哟,去河邊找鬼一忱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谭确,可吹牛的內(nèi)容都是我干的帘营。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼逐哈,長吁一口氣:“原來是場噩夢啊……” “哼芬迄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起昂秃,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤禀梳,失蹤者是張志新(化名)和其女友劉穎杜窄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體出皇,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡羞芍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了郊艘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荷科。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纱注,靈堂內(nèi)的尸體忽然破棺而出畏浆,到底是詐尸還是另有隱情,我是刑警寧澤狞贱,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布刻获,位于F島的核電站,受9級特大地震影響瞎嬉,放射性物質(zhì)發(fā)生泄漏蝎毡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一氧枣、第九天 我趴在偏房一處隱蔽的房頂上張望沐兵。 院中可真熱鬧,春花似錦便监、人聲如沸扎谎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽毁靶。三九已至,卻和暖如春逊移,著一層夾襖步出監(jiān)牢的瞬間预吆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工螟左, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啡浊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓胶背,卻偏偏與公主長得像巷嚣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子钳吟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

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

  • 一廷粒、簡述 Optional 的完整路徑是 java.util.Optional,使用它是為了避免代碼中的if (n...
    Djbfifjd閱讀 786評論 0 7
  • NullPointerException——空指針異常是程序中常見異常之一涤姊,也是導(dǎo)致程序運(yùn)行失敗的常見異常。以前嗤放,...
    程序員Mark_Chou閱讀 354評論 0 0
  • Option類型 Optional<T>是一個(gè)T對象的封裝思喊,比直接指向?qū)ο蟮囊酶踩_的使用情況下不會(huì)返回N...
    栗子葉閱讀 256評論 0 0
  • 1.當(dāng)我們還在以如下幾種方式使用 Optional 時(shí), 就得開始檢視自己了 調(diào)用 isPresent() 方法時(shí)...
    寇寇寇先森閱讀 41,812評論 3 12
  • 厭倦了空指針異常次酌? 考慮使用Java SE 8的Optional恨课!使代碼更具可讀性并使得免受空指針異常的影響。有人...
    bern85閱讀 432評論 0 2