一位同事請假了,接手他的程式碼8天了,要受不了,來看下同事 Optional 的使用:
Optional<User> userOption = Optional.ofNullable(userService.getUser(...)); if (!userOption.isPresent()) {....}
if 裡面還有 Optional 套 Optional ,連環判斷 isPresent 的。
關於 Optional 老早之前我就看到很多爭論,有好多怒噴 Optional 雞肋,是個糟糕的設計,巴拉巴拉。
先拋開這些不管,反正如果平日是按照以上的用法來用 Optional 的,還是直接用 if(user != null){....} 判空算了,何必包一層 Optional,再判斷呢?這樣使用 Optional 是不對滴,畫蛇添足。
那 Optional 應該如何用呢?
Optional 的真實執行邏輯是否與你所想的一樣?
今天同樣還是深入原始碼看看。
我們先來看看 Optional 設計出來的意圖是什麼, Java 語言架構師 Brian Goetz 是這麼說的:
Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.
意思就是:Optional 可以給返回結果提供了一個表示無結果的值,而不是返回 null。
簡單理解下,Optional 其實就是一個殼,裡面放著原先的值,至於這個值是不是 null 另說,反正拿到的這個殼肯定不是 null。
網上比較流行的說法是 Optional 可以避免空指標,我不太贊同這種說法。因為最終的目的是拿到 Optional 裡面儲存的值,如果這個值是 null ,不做額外的判斷,直接使用還是會有空指標的問題。
我認為 Optional 的好處在於可以簡化平日裏一系列判斷 null 的操作,使得用起來的時候看著不需要判斷 null,縱享絲滑,表現出來好像用 Optional 就不需要關心空指標的情況。
而事實上是 Optional 在替我們負重前行,該有的判斷它替我們完成了,而且用了 Optional 最後拿結果的時候還是小心的,盲目 get 一樣會拋錯,Brian Goetz 說 get 應該叫 getOrElseThrowNoSuchElementException。
我們來看一下程式碼就很清楚 Optional 的好處在哪兒了。比如現在有個 yesSerivce 能 get 一個 Yes,此時需要輸出 Yes 所在的省,此時的程式碼是這樣的:
Yes yes = getYes(); if (yes != null) { Address yesAddress = yes.getAddress(); if (yesAddress != null) { Province province = yesAddress.getProvince(); System.out.println(province.getName()); } } throw new NoSuchElementException(); //如果沒找到就拋錯
如果用 Optional 的話,那就變成下面這樣:
Optional.ofNullable(getYes()) .map(a -> a.getAddress()) .map(p -> p.getProvince()) .map(n -> n.getName()) .orElseThrow(NoSuchElementException::new);
可以看到,如果用了 Optional,程式碼裡不需要判空的操作,即使 address 、province 為空的話,也不會產生空指標錯誤,這就是 Optional 帶來的好處!
說到這,我想提個問:
如果在 a.getAddress() 時拿不到值的話,你說是會繼續執行map(p -> p.getProvince()) 還是直接跳到 orElseThrow? 或者反過來如果 map(n -> n.getName()) 不為空,你說 orElseThrow 這個方法會不會執行?
接下來我們就來看下原始碼,看看 Optional 的實現機制。
Optional 原始碼
Optional 的程式碼十分簡短且簡單,如果去掉註釋,我估計就100來行。
來看下幾個關鍵的成員變數:
符合前面提到的:Optional 就是個殼,裡面的 value 纔是正主。並且內建了一個 EMPTY 物件,用來替換當 value 為 null 時候的殼。
現在看下上面演示的 map 方法,看看它的內部實現是如何讓我們不需要做非空判斷的。
可以看到很簡單,沒幾行程式碼,我把方法中的兩個呼叫實現都貼上去,這樣對著看應該會更清晰:
先判斷 value 是否為空,如果是空的話說明真正要是值是空的,此時直接返回一個 empty(),還記得上面的 empty 方法吧?直接方式事先建立的空 Optional 。
如果 value 不為空,那說明值是存在的,因此呼叫 mapper (就是上面我們寫的 a.getAddress 之類的)來操作一波這個 value,並且用 Optional.ofNullable 包了一層,這個方法內部也看到了,如果 value 是空的話,也是返回空 Optional,否則就利用 of 包裹 value 成 Optional 返回。
因此,不論你 Optional 裡面到底有沒有值,我 map 都能處理!如果你是空,我就返回空 Optional ,如果你有值,ok 我包裹成 Optional 返回,反正不論怎樣,呼叫 map 的返回值都會是一個 Optional,而不是 null,所以執行時不會產生空指標的情況。
還記得上面的提問嗎?結合 map 的原始碼,現在來回答下上面的問題,看註釋:
截個 orElseThrow 的實現,就是判斷下 value ,如果是 null 就拋錯。
結合原始碼我們知道了答案:即使 Optional.ofNullable 返回的是空 Optional ,下面的 map 邏輯還是會執行,不會因為中間得到空值而直接跳到orElseThrow執行,這和我們平日知曉的 if else 邏輯不太一樣,不為空orElseThrow也一樣會執行,就是判斷 value!= null然後直接返回 value 的值了。
好了,邏輯就是這麼簡單!上面之所以說是 Optional 在替我們負重前行,是因為該有的判斷一個都沒少,只是它替我們做了而已。
關於 Optional 還有個效能問題,我們看一下:
Optional 裡有 orElseGet 和 orElse 這兩個看起來挺相似的方法,都是處理當值為 null 時的兜底邏輯。可能你也在一些文章上看到說用 orElseGet 不要用 orElse ,因為在 Optional 有值時候 orElse 仍然會呼叫方法,所以後者效能比較差。其實從上面分析我們知道不論 Optional 是否有值,orElse 和 orElseGet 都會被執行,所以是怎麼回事呢?
看下這個程式碼:
這樣看來 orElse 確實性能會差,奇怪了,難道是 bug?
我們來看下原始碼。
可以看到兩者的入參不同,一個就是普通引數,一個是 Supplier。我們已經得知不論Optional.ofNullable 返回的是否是空 Optional,下面的邏輯還是會執行,所以 orElse 和 orElseGet 這兩個方法無論如何都會執行。
因此 orElse(createYes()) 會被執行,在引數入棧之前,執行了 createYes 方法得到結果,然後入棧,而 orElseGet 的引數是 Supplier,所以直接入棧,然後在呼叫 other.get 的時候,createYes 方法纔會被觸發執行,這就是兩者的區別之處。
所以纔會造成上面表現出的效能問題,因此不是 BUG,也不是有些文章說的 Optional 有值 orElse 也會被執行而 orElseGet 不會執行這樣不準確的說法,相信現在你的心裏很有數了。
既然都講到這了,把 Optional 剩下幾個方法講講完吧,沒幾個了。
來看個 of 和 ofNullable 的對比,看下注釋應該很清晰了。
再來看個 isPresent() 和 ifPresent(Consumer<? super T> consumer),兩者名字有細微的差別,is 和 if。
還有個 get,這個方法要小心,如果沒做好判斷,直接呼叫,當是空 Optional 時會拋錯的。
還有個 filter邏輯 和 map 的差不多,用於過濾資料,平日基本是就是先 filter 再 map,屬於基操。
還有個 flatMap ,這個和 map 邏輯一模一樣,就入參有點不一樣,用在返回值不是普通物件,是 Optional 包裹的物件的場景。
這裏又得提一點了,關於 POJO 裡面的屬性是否應該被 Optional 包裹,或者說是否應該把 get 方法包裹成 Optional 返回,類似下面這樣的程式碼。
在 stackoverflow 有個類似的提問。
Brian Goetz 給了回答,我直接翻譯了:你可能永遠不應該將它用於返回結果陣列或結果列表的內容,而應該返回空陣列或空列表。你幾乎不應該將它用作某個欄位或方法引數,我認為經常使用它作為 getters 的返回值肯定是過度使用。
下面也有一堆不服的,說這發言更像是您自己所認為的,而沒有什麼依據表明這樣用有什麼不好,反正我不敢發言,神仙打架瑟瑟發抖。
不過我個人傾向於 Brian Goetz,我覺得 Optional 的用處就是邏輯處理的時候避免判空,僅此而已,所以 POJO 本該如何還是如何,Optional 應該交由邏輯處理程式碼來用。
好了,把 Optional 的方法都講完了,可以看到還是很簡單的,也沒有什麼騷操作,比看併發包的簡單多了。
總結下來 Optional 主要是簡化一系列判空操作,執行過程是一條龍走到底的,你有幾個 filter 和 map 不論得到的值空不空,都是執行到底包括 orElse 的邏輯。
再提一個題外話,在 oracle 官網上我看到一篇關於 Optional 的文章,上面寫道:
像 Groovy 是利用 ?. 來避免判空的,例如這個程式碼:
String version = computer?.getSoundcard()?.getUSB()?.getVersion();
後面寫了個 note: 請注意,它很快也將被包含在c#中,它曾被提議用於Java SE 7,但沒有在那個版本中實現。
咱也不知道為啥沒被接受,反正我覺得上面這寫法挺清爽的。
再分享下網上看到的一副圖:
來源dzone