瞭解、接受和利用Java中的Optional (類)

本文轉自微信號EAWorld。掃描下方二維碼,關注成功後,回覆“普元方法+”,將會獲得熱門課堂免費學習機會!本文轉自微信號EAWorld。

1.概述

Java 8 最有趣的特性之一,就是引入了全新的 Optional 類。該類主要用來處理幾乎每位程序員都碰到過的麻煩問題—— 空指針異常(NullPointerException)。

從本質上來說,該類屬於包含可選值的封裝類(wrapper class),因此它既可以包含對象也可以僅僅爲空。

伴隨着 Java函數式編程方式的異軍突起,Optional 應運而生,除了可助該編程方式一臂之力外,Optional 的作用顯然還遠不止於此。

我們先來看一個簡單的案例。在 Java 8 之前,凡涉及到訪問對象方法或者對象屬性的操作,無論數量多寡,都可能導致 空指針異常:

圖片描述

假如我們想保證上面的小示例不出現異常,我們可能需要在訪問它之前對每一個值進行顯式檢查:

圖片描述

這麼一來,會讓代碼顯得累贅而難以維護。

爲簡化這一過程,我們將使用 Optional 類取代上述代碼,從創建和驗證一個實例開始,再到使用其提供的不同方法,最後將其和返回相同類型的其他方法進行組合,而最後這項組合功能正是 Optional 的真正強大之處。

2.創建 Optional 實例

爲了實現重複迭代(reiterate),該類型對象既可以包含一個值,也可以爲空。我們先用具有相同名稱的方法來創建一個空 Optional:

圖片描述

毫無疑問,如果您要訪問 emptyOpt 變量的值,會導致 NoSuchElementException異常。

您可以用 of() 和 ofNullable(),來創建包含一個值的Optional 對象。兩種方法的區別在於:如果你將 null 值作爲參數傳入 of() 方法,那麼該方法會拋出一個 空指針異常。

圖片描述
如你所見,空指針 異常的問題並沒有得到徹底解決。因此,只有當對象不爲 null 時, of()的方法纔可行。

如果對象既可能爲 null ,也可能爲非 null ,就必須選擇 ofNullable()。

圖片描述

訪問 Optional 對象的值

想要獲取Optional實例內部的對象,方法之一是使用get()方法

圖片描述

但和之前類似,這種方法在值爲 null 時也會拋出異常。爲避免出現異常,您可選擇首先檢驗其中是否存在值。

圖片描述

利用 ifPresent()也可以用來檢查是否存在值。而且該方法還帶有一個 Consumer 參數,在對象不爲空時執行 λ 表達式:

圖片描述

在此示例中,只有在用戶對象非空時,纔會執行assertion。

接下來,我們看看能夠替換空值的各種方法。

返回默認值

Optional 類提供了一些 API,用於返回對象值或在對象爲空時返回默認值。
其中的第一種方法是 orElse(),它的工作方式相當直接:如果存在值,則返回該值,如果不存在值,則返回它收到的參數:

圖片描述

此處,user 對象爲空,所以 user2 作爲默認替代值返回。

如果對象的初始值不爲空,則默認值會被忽略:

圖片描述

第二種同類 API 是 orElseGet() ——其工作方式略有不同。在本例中,如果存在值,則方法回返該值,如果不存在,則其執行 Supplier 函數接口(作爲其收到的一個參數),並返回執行結果:

圖片描述

orElse() 和 orElseGet() 之間的區別

乍一看,兩種方法似乎效果相同。但實際還是有差別。我們可以通過創建幾個例子,來看看二者在功能表現上的相似處和不同點。

首先,我們來看對象爲空時,二者的表現:

圖片描述

在上面的代碼中,兩種方法都調用了createNewUser() 方法,後者會記錄消息日誌並返回 User 對象。

代碼輸出如下:

圖片描述

可見,當對象爲空時,二者在表現上並無差別,都是代之以返回默認值。

接下來,我們舉一個 Optional 不爲空時的相似例子:

圖片描述

這次的輸出如下:

圖片描述
此處,兩個 Optional 對象都包含有一個非空值,而兩種方法都會將其作爲返回值。但是,orElse() 方法仍然會創建默認的 User 對象。相反,orElseGet() 方法將不再創建 User 對象。

當操作中包含大量密集調用時,比如 web 服務調用或者數據庫查詢,這種差別就會對代碼執行產生重大影響。

返回異常

除了 orElse() 和 orElseGet() 方法,Optional還定義了 ElseThrow() API,其作用是在對象爲空時,直接拋出一個異常,而不是返回一個替代值。

圖片描述

此處,如果 user 值爲空,則會拋出 非法參數異常。

這讓我們可以從更多靈活的語義中挑選所要拋出的異常,而不是千篇一律的 空指針異常。

既然我們已對 Optional 本身的使用有了一定了解,那就讓我們再來看看用於轉換和過濾 Optional 值的其他方法。

3.對值進行轉換

Optional 值可通過多種方法進行轉換;我們就從 map() 和 flatMap() 說起。

首先,讓我們看個使用 map() API 的例子:

圖片描述

Map() 將 Function 參數作爲值,然後返回 Optional 中經過封裝的結果。這將使我們可以在後續附加一些操作,比如此處的 orElse() 。

相比之下,flatMap() 也是將 Function 參數作爲 Optional 值,但它後面是直接返回結果。

爲了查看實際效果,我們添加一個方法,可向 User 類返回 Optional:

圖片描述

因爲 getter 方法返回一個 Optional 字符串值,在請求Optional User 對象時,您可將其作爲 flatMap() 的參數。返回值爲非封裝字符串值:

圖片描述

4.對值進行過濾

除了對值進行轉換的功能,Optional 類還提供了根據條件對值進行“過濾”的功能。

filter() 方法將 predicate 作爲參數,當測試評估爲真時,返回實際值。否則,當測試爲假時,返回值則爲空 Optional。

我們來看一個例子——基於非常基本的電子郵件驗證,接受或者拒絕 User:

圖片描述
作爲通過過濾測試的結果,Result 對象將包含一個非 null 值。

5.對 Optional 類的方法進行鏈接

Optional 還具有更多強大的應用,鑑於絕大多數 Optional 方法會返回相同類型的對象,您可以將它們的不同組合鏈接起來。

我們把示例代碼重新寫一下。

首先,我們重構這些類,這樣 getter 方法將返回 Optional 引用

圖片描述
圖片描述

上述結構可用嵌套集合來直觀地表示:

圖片描述

現在刪除對 null 進行檢查的代碼,並以 Optional 方法來取代:

圖片描述
上面的代碼可通過方法引用(method references)做進一步精簡:

圖片描述

從現在的結果看,代碼比先前冗長的條件驅動(conditional-driven)版本要簡潔許多。

6.Java 9 新增特性

在 Java 8 引入Optional特性的基礎上,Java 9 又爲 Optional 類增加了三種方法:or()、ifPresentOrElse() 和 stream()。

在某種意義上,or() 方法同 orElse() 和 orElseGet() 類似,都是在對象爲空時提供替換功能。在本例中,返回值爲另一個由 Supplier 參數生成的 Optional 對象。

如果對象包含一個值,則λ表達式不會執行:
圖片描述

在上述示例中,如果 user 變量爲空,則將返回包含一個帶有電子郵件“default”的 User 對象的 Optional 。

ifPresentOrElse() 方法帶有兩個參數:Consumer 和 Runnable。如果對象包含一個值,則會執行 Consumer 動作;否則,會執行 Runnable 動作。

如果您希望使用某個現有值執行一個動作,或者僅僅想跟蹤某個值是否已作定義,則該方法非常有用:

圖片描述

最後,您可從新 stream() 方法的擴展 Stream API 得到益處,具體做法是將實例轉換爲一個 Stream 對象。如果 Optional 不存在值,則 Stream 爲空,如果 Optional 包含一個非 null 值,則 Stream 會包含單個值。

我們舉個將 Optional 作爲 Stream 處理的例子:

圖片描述

在此處使用 Stream ,使得應用 filter()、map() 和 collect() 等 Stream 接口方法 來獲取 List 成爲可能。

7.應該如何使用 Optional

在使用 Optional 時,我們需要考慮幾個問題,來決定什麼時候用以及如何用。

第一個要點,Optional 並不能序列化(Serializable )。因此,它不可以在類中當作一個字段(field)來使用。

如果您需要序列化一個包含 Optional 值的對象,Jackson library(https://stackify.com/java-xml-jackson/)可支持將 Optionals當作普通對象來對待。這意味着,Jackson 會將空對象作爲 null,它還會將有值對象當作一個包含該值的字段。這個功能可在 jackson-modules-java8 (https://github.com/FasterXML/jackson-modules-java8) 項目中找到。

另一種不太適合使用該類型的情況,是將該類型作爲方法或者構造函數的參數。這將導致不必要的代碼複雜化。

圖片描述

相反,使用方法重載(method overloading)來處理非強制性參數要方便得多。

Optional的主要用途是作爲一種返回類型。在獲得該類型的一個實例後,如果存在值,您可以提取該值,如果不存在值,則您可以獲得一個替換值。

Optional類對我們最有幫助的一個用例,是其同 stream 或者其他方法的組合使用,這些方法會返回一個可構建流暢 API 的Optional 值。

我們舉個使用 Stream findFirst() 方法並返回 Optional 對象的例子:

圖片描述

8.總結

對於 Java 語言來說,Optional 是一項非常有用的新增特性。儘管無法徹底消除 空指針異常,但 Optional 可以最大限度減少代碼執行過程中出現的此類異常。

同時,該類經過精心設計,對於 Java 8 加入的新函數式支持(functional support)而言,它自然而然地成爲非同一般的新增特性。

總之,該類簡單而不失強大,相比之前的同類功能,用其編寫代碼既簡單易讀又不易出錯。

原文鏈接:https://stackify.com/optional-java/

關於作者:
Eugen是一名軟件工程師,對Spring、REST API、安全和教育擁有極大熱情。同時,他還是Baeldung(推特賬號@baeldung)的創始人。

關於EAWorld
微服務,DevOps,元數據,企業架構原創技術分享,EAii(Enterprise Architecture Innovation Institute)企業架構創新研究院旗下官方微信公衆號。
掃描下方二維碼,關注成功後,回覆“普元方法+”,將會獲得熱門課堂免費學習機會!
微信號:EAWorld,長按二維碼關注。

圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章