Guava之Optional原理

在我們學(xué)習(xí)和使用Guava的Optional之前蠢棱,我們需要來了解一下Java中null。因為覆享,只有我們深入的了解了null的相關(guān)知識靶橱,我們才能更加深入體會領(lǐng)悟到Guava的Optional設(shè)計和使用上的優(yōu)雅和簡單寥袭。

NullPointerException,大家應(yīng)該都見過。這是Tony Hoare在設(shè)計ALGOL W語言時提出的null引用的想法关霸,他的設(shè)計初衷是想通過編譯器的自動檢測機制传黄,確保所有使用引用的地方都是絕對安全的。很多年后队寇,他對自己曾經(jīng)做過的這個決定而后悔不已膘掰,把它稱為“我價值百萬的重大失誤”。它帶來的后果就是---我們想判斷一個對象中的某個字段進行檢查英上,結(jié)果發(fā)現(xiàn)我們查看的不是一個對象炭序,而是一個空指針啤覆,他會立即拋出NullPointerException異常苍日。

Java中null的使用有時候會產(chǎn)生一些意想不到的內(nèi)傷:
1.無法表達具體的業(yè)務(wù)含義,語義含糊不清窗声;
2.增加了NullPointException的發(fā)生相恃,因為不知道什么地方就返回了一個null;
3.null和空容易混為一談笨觅;
4.需要非null判斷拦耐,弱可讀性、代碼不夠優(yōu)雅见剩。返回一個null值絕對不是一個好的選擇杀糯,所以,對于null關(guān)鍵字盡量避免使用苍苞。

空指針是我們最常見也最討厭的異常固翰,為了防止空指針異常狼纬,你不得在代碼里寫大量的非空判斷。
null值是一種令人不滿的模糊含義骂际。有的時候會產(chǎn)生二義性疗琉,這時候我們就很難搞清楚具體的意思,如果程序返回一個null值歉铝,其代表的含義到底是什么盈简,例如:Map.get(key)若返回value值為null,其代表的含義可能是該鍵指向的value值是null太示,亦或者該鍵在map中并不存在柠贤。null值可以表示失敗,可以表示成功类缤,幾乎可以表示任何情況种吸。用其它一些值(而不是null值)可以讓你的代碼表述的含義更清晰。
ConcurrentHashMap不允許為空

The main reason that nulls aren’t allowed in ConcurrentMaps
(ConcurrentHashMaps, ConcurrentSkipListMaps) is that
ambiguities that may be just barely tolerable in non-concurrent
maps can’t be accommodated. The main one is that if
map.get(key) returns null, you can’t detect whether the
key explicitly maps to null vs the key isn’t mapped.
In a non-concurrent map, you can check this via map.contains(key),
but in a concurrent one, the map might have changed between calls.

Guava文檔中呀非,第一篇就提到的盡量避免使用Null坚俗,會給代碼帶來一些負面影響,并舉出map.get(key) == null岸裙,帶來的混淆猖败。由此。Guava提出了Optional的概念降允。

Guava用Optional表示可能為null的T類型引用恩闻。一個Optional實例可能包含非null的引用(我們稱之為引用存在),也可能什么也不包括(稱之為引用缺失)剧董。它從不說包含的是null值幢尚,而是用存在或缺失來表示。但Optional從不會包含null值引用翅楼。

image.png

Guava的Optional有兩種實現(xiàn)尉剩,Absent和Present,這就可以理解為毅臊,傳統(tǒng)代碼書寫方式中的null和non-null理茎。而Guava中Absent和Present中重寫Optional的isPresent()方法。

類聲明

@GwtCompatible(serializable = true)
public abstract class Optional<T> implements Serializable

Optional.of(T)

    public void test1(){
        Integer num = null;
        Optional<Integer> op1 = Optional.of(num); // java.lang.NullPointerException
        System.out.println(op1.get());
    }

上面的程序管嬉,我們使用Optional.of(null)方法皂林,這時候程序會第一時間拋出空指針異常,這可以幫助我們盡早發(fā)現(xiàn)問題蚯撩。如果給定值不為null础倍,則會返回給定值的Optional實例。

  public static <T> Optional<T> of(T reference) {
    return new Present<T>(checkNotNull(reference));
  }

首先使用checkNotNull來判斷給定值是否為null胎挎,如果為null沟启,則會拋出空指針異常扰楼,否則返回給定值的Optional的實例(Present是Optional的子類)。

Optional.absent()

    public void test3(){
        Integer num = new Integer(4);
        Optional<Integer> op = Optional.absent();
        Optional<Integer> op2 = Optional.of(num);
        System.out.println("op:" + op.isPresent() + "    op2:" + op2.isPresent());
    }

上面的程序美浦,我們使用Optional.absent()方法弦赖,創(chuàng)建引用缺失的Optional實例。 源碼:

  public static <T> Optional<T> absent() {
    return Absent.withType();
  }
 static final Absent<Object> INSTANCE = new Absent<Object>();
  @SuppressWarnings("unchecked") // implementation is "fully variant"
  static <T> Optional<T> withType() {
    return (Optional<T>) INSTANCE;
  }

通過withType方法返回一個靜態(tài)Absent對象浦辨,并強制轉(zhuǎn)換為Optional對象蹬竖。從上面就可以看出其中不包含任何的引用。

Optional.fromNullable(T)
創(chuàng)建指定引用的Optional實例流酬,若引用為null則表示缺失币厕,返回應(yīng)用缺失對象Absent,否則返回引用存在對象Present芽腾。

    public void test4(){
        Integer num1 = null;
        Integer num2 = new Integer(4);
        Optional<Integer> op1 = Optional.fromNullable(num1); // 引用缺失
        Optional<Integer> op2 = Optional.fromNullable(num2); // 引用存在
        System.out.println("op1:" + op1.isPresent() + "    op2:" + op2.isPresent()); // false true
    }
}
  public static <T> Optional<T> fromNullable(@Nullable T nullableReference) {
    return (nullableReference == null)
        ? Optional.<T>absent()
        : new Present<T>(nullableReference);
  }

從上面源碼中可以看出如果T為null旦装,則調(diào)用Optional靜態(tài)方法absent(),表示引用缺失摊滔;如果T不為null阴绢,則創(chuàng)建一個Present對象,表示引用存在艰躺。

T get()
返回Optional包含的T實例呻袭,該T實例必須不為空;否則腺兴,對包含null的Optional實例調(diào)用get()會拋出一個IllegalStateException異常左电。

    public void test5(){
        Integer num1 = null;
        Integer num2 = new Integer(4);
        Optional<Integer> op1 = Optional.fromNullable(num1); // 引用缺失
        Optional<Integer> op2 = Optional.fromNullable(num2); // 引用存在
        System.out.println("op2:" + op2.get()); // 4
        System.out.println("op1:" + op1.get()); // java.lang.IllegalStateException: Optional.get() cannot be called on an absent value
    }

因為fromNullable對象根據(jù)給定值是否為null,返回不同的對象:

return (nullableReference == null)
        ? Optional.<T>absent()
        : new Present<T>(nullableReference);

因此調(diào)用的get方法也將會不一樣页响。

  public abstract T get();

如果返回的是一個Present對象篓足,將調(diào)用Present類中的get()方法:

  @Override
  public T get() {
    return reference;
  }

如果返回的是一個Absent對象,將調(diào)用Absent類中的get()方法:

  @Override
  public T get() {
    throw new IllegalStateException("Optional.get() cannot be called on an absent value");
  }

T or (T)
返回Optional所包含的引用闰蚕,若引用缺失栈拖,返回指定的值。

    public void test6(){
        Integer num1 = null;
        Integer num2 = new Integer(4);
        Optional<Integer> op1 = Optional.fromNullable(num1); // 引用缺失
        Optional<Integer> op2 = Optional.fromNullable(num2); // 引用存在
        System.out.println("op2:" + op2.or(0)); // 4
        System.out.println("op1:" + op1.or(0)); // 0
    }

因為fromNullable對象根據(jù)給定值是否為null陪腌,返回不同的對象:

return (nullableReference == null)
        ? Optional.<T>absent()
        : new Present<T>(nullableReference);

因此調(diào)用的or方法也將會不一樣辱魁。

  public abstract T or(T defaultValue);

如果返回的是一個Present對象烟瞧,將調(diào)用Present類中的or()方法:

  @Override
  public T or(T defaultValue) {
    checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)");
    return reference;
  }

這個方法首先對默認值進行判斷诗鸭,如果不為null,則返回引用参滴;如果為null强岸,拋出空指針異常,這種情況可以使用Optional.orNull()方法代替砾赔。
(2)如果返回的是一個Absent對象蝌箍,將調(diào)用Absent類中的or()方法:

  @Override
  public T or(T defaultValue) {
    return checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)");
  }

這個方法首先對默認值進行判斷青灼,如果不為null,則返回默認值妓盲;如果為null杂拨,拋出空指針異常,這種情況可以使用Optional.orNull()方法代替悯衬。

    public void test6(){
        String num1 = null;
        String num2 = "123";
        String defaultNum = null;
        Optional<String> op1 = Optional.fromNullable(num1); // 引用缺失
        Optional<String> op2 = Optional.fromNullable(num2); // 引用存在
        System.out.println("op2:" + op2.or("0")); // 123
        System.out.println("op1:" + op1.or(defaultNum)); // java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
    }

T orNull()
返回Optional所包含的引用弹沽,若引用缺失,返回null

    public void test6(){
        String num1 = null;
        String num2 = "123";
        Optional<String> op1 = Optional.fromNullable(num1); // 引用缺失
        Optional<String> op2 = Optional.fromNullable(num2); // 引用存在
        System.out.println("op2:" + op2.orNull()); // 123
        System.out.println("op1:" + op1.orNull()); // null
    }

因為fromNullable對象根據(jù)給定值是否為null筋粗,返回不同的對象:

return (nullableReference == null)
        ? Optional.<T>absent()
        : new Present<T>(nullableReference);

因此調(diào)用的orNull方法也將會不一樣策橘。

  @Nullable
  public abstract T orNull();

(1)如果返回的是一個Present對象,將調(diào)用Present類中的orNull()方法:

  @Override
  public T orNull() {
    return reference;
  }

引用存在娜亿,返回引用丽已。

(2)如果返回的是一個Absent對象,將調(diào)用Absent類中的orNull()方法:

  @Override
  @Nullable
  public T orNull() {
    return null;
  }

引用缺失买决,返回null沛婴,此時沒有默認值。

注意事項

不要在Set中使用null督赤,或者把null作為map的鍵值瘸味。使用特殊值代表null會讓查找操作的語義更清晰。

如果你想把null作為map中某條目的值够挂,更好的辦法是 不把這一條目放到map中旁仿,而是單獨維護一個”值為null的鍵集合” (null keys)。Map 中對應(yīng)某個鍵的值是null孽糖,和map中沒有對應(yīng)某個鍵的值枯冈,是非常容易混淆的兩種情況。因此办悟,最好把值為null的鍵分離開尘奏,并且仔細想想,null值的鍵在你的項目中到底表達了什么語義病蛉。

使用Optional的意義在哪兒炫加?

  • Optional 迫使你積極思考引用缺失的情況 因為你必須顯式地從Optional獲取引用。
  • 如同輸入?yún)?shù)铺然,方法的返回值也可能是null俗孝。和其他人一樣,你絕對很可能會忘記別人寫的方法method(a,b)會返回一個null魄健,就好像當你實現(xiàn)method(a,b)時赋铝,也很可能忘記輸入?yún)?shù)a可以為null。將方法的返回類型指定為Optional沽瘦,方法的參數(shù)設(shè)置為Optional革骨,也可以迫使調(diào)用者思考返回的引用缺失的情形农尖。
 public static Optional<Integer> sum(Optional<Integer> a,Optional<Integer> b){
        if(a.isPresent() && b.isPresent()){
            return Optional.of(a.get()+b.get());
        }
        return Optional.absent();
    }

java8 optional實戰(zhàn)

以前寫法

public String getCity(User user)  throws Exception{
        if(user!=null){
            if(user.getAddress()!=null){
                Address address = user.getAddress();
                if(address.getCity()!=null){
                    return address.getCity();
                }
            }
        }
        throw new Excpetion("取值錯誤"); 
    }

JAVA8寫法 (java8Optional就是受guava Opitonal啟發(fā))

public String getCity(User user) throws Exception{
    return Optional.ofNullable(user)
                   .map(u-> u.getAddress())
                   .map(a->a.getCity())
                   .orElseThrow(()->new Exception("取指錯誤"));
}

以前寫法

if(user!=null){
    dosomething(user);
}

JAVA8寫法

 Optional.ofNullable(user)
    .ifPresent(u->{
        dosomething(u);
});

以前寫法

public User getUser(User user) throws Exception{
    if(user!=null){
        String name = user.getName();
        if("zhangsan".equals(name)){
            return user;
        }
    }else{
        user = new User();
        user.setName("zhangsan");
        return user;
    }
}

現(xiàn)在寫法

public User getUser(User user) {
    return Optional.ofNullable(user)
                   .filter(u->"zhangsan".equals(u.getName()))
                   .orElseGet(()-> {
                        User user1 = new User();
                        user1.setName("zhangsan");
                        return user1;
                   });
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市良哲,隨后出現(xiàn)的幾起案子盛卡,更是在濱河造成了極大的恐慌,老刑警劉巖筑凫,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窟扑,死亡現(xiàn)場離奇詭異,居然都是意外死亡漏健,警方通過查閱死者的電腦和手機嚎货,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔫浆,“玉大人殖属,你說我怎么就攤上這事⊥呤ⅲ” “怎么了洗显?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長原环。 經(jīng)常有香客問我挠唆,道長,這世上最難降的妖魔是什么嘱吗? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任玄组,我火速辦了婚禮,結(jié)果婚禮上谒麦,老公的妹妹穿的比我還像新娘俄讹。我一直安慰自己,他們只是感情好绕德,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布患膛。 她就那樣靜靜地躺著,像睡著了一般耻蛇。 火紅的嫁衣襯著肌膚如雪踪蹬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天臣咖,我揣著相機與錄音跃捣,去河邊找鬼。 笑死亡哄,一個胖子當著我的面吹牛枝缔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚊惯,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼愿卸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了截型?” 一聲冷哼從身側(cè)響起趴荸,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宦焦,沒想到半個月后发钝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡波闹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年酝豪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片精堕。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡孵淘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出歹篓,到底是詐尸還是另有隱情瘫证,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布庄撮,位于F島的核電站背捌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏洞斯。R本人自食惡果不足惜毡庆,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烙如。 院中可真熱鬧扭仁,春花似錦、人聲如沸厅翔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刀闷。三九已至熊泵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甸昏,已是汗流浹背顽分。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留施蜜,地道東北人卒蘸。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缸沃。 傳聞我的和親對象是個殘疾皇子恰起,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

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