首頁/ 汽車/ 正文

跟著 Guava 學 Java 之 Optional

使用和避免 null

Google 底層程式碼庫,95%的集合類不接受 null 值作為元素。 相比默默地接受 null,使用快速失敗操作拒絕 null 值對開發者更有幫助。

很多 Guava 工具類對 Null 值都採用快速失敗操作,此外,Guava 還提供了很多工具類,讓你更方便地用特定值替換 Null 值

例子

我們知道 JDK8 以後 也參考 Guava 加入了

Optional

的 API,使用上跟 Guava 的區別不大,例子中我們使用 JDK 的 API 來演示。

直接上個實際工作中的案例即:

“物件的巢狀判空”

比如我有個物件,物件的某個屬性也是物件,然後就這樣一直巢狀下去,比如:

@Datapublic class Test1{ private String info=“info1”; private Test2 test2;}@Datapublic class Test2 { private String info; private Test3 test3;}@Datapublic class Test3 { private String info; private Test4 test4;}@Datapublic class Test4 { private String info = “test4 info”;}

為了減少程式碼量和版面,我使用了 Lombok

org。projectlombok lombok 1。18。24 provided

如果我想使用 Test4 的 info 屬性,可以用 if 一直巢狀判斷下來:

if (test1 != null) { Test2 test2 = test1。getTest2(); if (test2 != null) { Test3 test3 = test2。getTest3(); if (test3 != null) { Test4 test4 = test3。getTest4(); if (test4 != null) { System。out。println(test4。getInfo()); } } }}

跟著 Guava 學 Java 之 Optional

物件層級一深,程式碼很臃腫。

Optional

可以幫我們用一行程式碼解決掉!

String info1 = Optional。ofNullable(test1) 。map(Test1::getTest2) 。map(Test2::getTest3) 。map(Test3::getTest4) 。map(Test4::getInfo) 。orElse(“hello”);System。out。println(info1);

這行程式碼達到的效果和上面的 if 一樣

解釋一下上面幾個

Optional

的方法:

ofNullable : 如果 test 為空,則返回一個單例空 Optional 物件,如果非空則返回一個 Optional 包裝物件,Optional 將 test 包裝

map: 如果為空,繼續返回第一步中的單例 Optional 物件,否則呼叫 Test 的 getTest 方法;

orElst: 獲得 map 中的 value,不為空則直接返回 value,為空則返回傳入的引數作為預設值

上面程式碼中的 map 方法也可以換作 flatMap 方法,區別是 :

flatMap 要求返回值為 Optional 型別,而 map 不需要,flatMap 不會多層包裝,map 返回會再次包裝 Optional。

我們這裡 Test 類是普通類並沒有使用

Optional

包裝,如果這麼寫就可以使用 flatMap:

@Datapublic class Test1 { private String info = “info1”; private Optional test2;}

此外我們還可以在發生空指標的情況下,丟擲異常或自定義異常:

String info2 = Optional。of(test1) 。map(Test1::getTest2) 。map(Test2::getTest3) 。map(Test3::getTest4) 。map(Test4::getInfo) 。orElseThrow(() -> new RuntimeException(“有空指標異常,物件內容為: ” + ToStringBuilder。reflectionToString(test1,new MultilineRecursiveToStringStyle())));System。out。println(info2);

可能你注意到了,我這裡的異常輸出中用到了

ToStringBuilder

這個類,這個是 Apache Commons Lang3 的庫類

org。apache。commons commons-lang3 3。12。0

使用它的原因是:我們利用

Optional

一行程式碼就可以判斷很多空指標是不錯,

但最後就算能捕捉異常也不能確定到底是哪個物件的哪個屬性為空,如果只是籠統的給出頂層物件的異常資訊,對於排錯還是不很直觀

。當然如果要非常細緻地判斷和列印日誌又會加大程式碼量,所以想了個折中的辦法:

將物件的資訊遞迴地打印出來

,這樣是不是空在排查的時候就一目瞭然了。

ToStringBuilder。reflectionToString

方法可以幫我做到。

ToStringBuilder。reflectionToString(test1, new MultilineRecursiveToStringStyle())

MultilineRecursiveToStringStyle

也可替換為

RecursiveToStringStyle

,只是不同的顯示 Style 罷了

如果我只有 test1 物件不為空,剩下的都為空,那麼列印結果如下:

com。xiaobox。gauva。test。Test1@2a5ca609[ info=info1, test2=

如果我的 test1 和 test2 物件都不為空,那麼列印結果如下:

com。xiaobox。gauva。test。Test1@2a5ca609[ info=info1, test2=com。xiaobox。gauva。test。Test2@26be92ad[ info=, test3= ]]

這樣的話,我就可以把資訊合併到異常資訊中,在排查問題時可以藉助這些資訊快速定位到哪個物件或哪個屬性為空了。

注意

: 上面的例子中丟擲了異常,但因為不是受檢異常,所以 IDE 並沒有提示我進行捕捉,寫程式碼有些時候忘了捕獲異常,所以,請記得它是將異常

throws

出去了。處理異常的時候別忘了。

跟著 Guava 學 Java 之 Optional

比如一般我們可以這樣

private static void opExceptionMethod() throws Exception { String info2 = Optional。of(test1) 。map(Test1::getTest2) 。map(Test2::getTest3) 。map(Test3::getTest4) 。map(Test4::getInfo) 。orElseThrow(() -> new RuntimeException(“有空指標異常,物件內容為: ” + ToStringBuilder。reflectionToString(test1, new MultilineRecursiveToStringStyle()))); System。out。println(info2);}

顯示地丟擲,呼叫者就必須要捕獲處理了。 當然也可以不丟擲,直接在程式碼塊 try catch

防禦性程式設計

使用 Optional 除了賦予 null 語義,增加了可讀性,最大的優點在於它是一種傻瓜式的防護。Optional 迫使你積極思考引用缺失的情況,因為你必須顯式地從 Optional 獲取引用。直接使用 null 很容易讓人忘掉某些情形,儘管 FindBugs 可以幫助查詢 null 相關的問題,但是我們還是認為它並不能準確地定位問題根源。

如同輸入引數,方法的返回值也可能是 null。和其他人一樣,你絕對很可能會忘記別人寫的方法 method(a,b) 會返回一個 null,就好像當你實現 method(a,b) 時,也很可能忘記輸入引數 a 可以為 null。將方法的返回型別指定為 Optional,也可以迫使呼叫者思考返回的引用缺失的情形。

關於 null 的建議

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

如果你想把 null 作為 map 中某條目的值,更好的辦法是 不把這一條目放到 map 中,而是單獨維護一個”值為 null 的鍵集合” (null keys)

其他

從 Spring 5 開始,可以使用 null 安全註解來幫助編寫更安全的程式碼。 此功能稱為“空安全性”,這是一組註解,其作用類似於監視潛在的空引用的安全措施。

空安全功能不是讓擺脫不安全的程式碼,而是在編譯時生成警告。 這樣的警告可以防止在執行時發生災難性的空指標

注意這些註解只會發出警告,由於有了這個提示,可以提前發現問題,並能夠採取適當的措施來避免執行時失敗,也就是說你還是可以傳遞 null 值進來 。

@NonNull 註解 : 可以在需要物件引用的任何地方使用此註解宣告非 null 約束:欄位,方法引數或方法的返回值。

@NonNullFields 註解 : 包(Package)級別註解,通知開發工具預設情況下,帶註釋的包中的所有欄位均為非空。

@Nullable 註解:有時,希望免除某些欄位,使其不受程式包級別指定的非 null 約束的約束。

@NonNullApi 註解 : 包(Package)級別註解,@NonNullFields 僅僅適用於欄位。 如果希望對方法的

引數和返回值

產生相同的影響,則需要@NonNullApi, 此註解只適用於包級別

看一個例子:

這是 Spring 框架中 Spring-Core 的

package-info

檔案內容

路徑為:

/org/springframework/spring-core/5。2。15。RELEASE/spring-core-5。2。15。RELEASE-sources。jar!/org/springframework/core/package-info。java

@NonNullApi@NonNullFieldspackage org。springframework。core;import org。springframework。lang。NonNullApi;import org。springframework。lang。NonNullFields;

參考

https://coolshell。cn/articles/17757。html

https://juejin。cn/post/6844903718375129095

https://blog。csdn。net/niugang0920/article/details/116291106

相關文章

頂部