鍍金池/ 教程/ Java/ 使用和避免 null
不可變集合
排序: Guava 強(qiáng)大的”流暢風(fēng)格比較器”
強(qiáng)大的集合工具類:java.util.Collections 中未包含的集合工具
新集合類型
常見 Object 方法
I/O
前置條件
字符串處理:分割,連接,填充
散列
原生類型
數(shù)學(xué)運(yùn)算
使用和避免 null
Throwables:簡(jiǎn)化異常和錯(cuò)誤的傳播與檢查
google Guava 包的 ListenableFuture 解析
事件總線
緩存
函數(shù)式編程
區(qū)間
集合擴(kuò)展工具類
Google-Guava Concurrent 包里的 Service 框架淺析
google Guava 包的 reflection 解析

使用和避免 null

Doug Lea 說,“Null 真糟糕?!?/p>

當(dāng) Sir C. A. R. Hoare 使用了 null 引用后說,”使用它導(dǎo)致了十億美金的錯(cuò)誤?!?/p>

輕率地使用 null 可能會(huì)導(dǎo)致很多令人驚愕的問題。通過學(xué)習(xí) Google 底層代碼庫(kù),我們發(fā)現(xiàn) 95%的集合類不接受 null 值作為元素。我們認(rèn)為, 相比默默地接受 null,使用快速失敗操作拒絕 null 值對(duì)開發(fā)者更有幫助。

此外,Null 的含糊語義讓人很不舒服。Null 很少可以明確地表示某種語義,例如,Map.get(key)返回 Null 時(shí),可能表示 map 中的值是 null,亦或 map 中沒有 key 對(duì)應(yīng)的值。Null 可以表示失敗、成功或幾乎任何情況。使用 Null 以外的特定值,會(huì)讓你的邏輯描述變得更清晰。

Null 確實(shí)也有合適和正確的使用場(chǎng)景,如在性能和速度方面 Null 是廉價(jià)的,而且在對(duì)象數(shù)組中,出現(xiàn) Null 也是無法避免的。但相對(duì)于底層庫(kù)來說,在應(yīng)用級(jí)別的代碼中,Null 往往是導(dǎo)致混亂,疑難問題和模糊語義的元兇,就如同我們舉過的 Map.get(key)的例子。最關(guān)鍵的是,Null 本身沒有定義它表達(dá)的意思。

鑒于這些原因,很多 Guava 工具類對(duì) Null 值都采用快速失敗操作,除非工具類本身提供了針對(duì) Null 值的因變措施。此外,Guava 還提供了很多工具類,讓你更方便地用特定值替換 Null 值。

具體案例

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

如果你想把 null 作為 map 中某條目的值,更好的辦法是 不把這一條目放到 map 中,而是單獨(dú)維護(hù)一個(gè)”值為 null 的鍵集合” (null keys)。Map 中對(duì)應(yīng)某個(gè)鍵的值是 null,和 map 中沒有對(duì)應(yīng)某個(gè)鍵的值,是非常容易混淆的兩種情況。因此,最好把值為 null 的鍵分離開,并且仔細(xì)想想,null 值的鍵在你的項(xiàng)目中到底表達(dá)了什么語義。

如果你需要在列表中使用 null——并且這個(gè)列表的數(shù)據(jù)是稀疏的,使用 Map<Integer, E>可能會(huì)更高效,并且更準(zhǔn)確地符合你的潛在需求。

此外,考慮一下使用自然的 null 對(duì)象——特殊值。舉例來說,為某個(gè) enum 類型增加特殊的枚舉值表示 null,比如 java.math.RoundingMode 就定義了一個(gè)枚舉值 UNNECESSARY,它表示一種不做任何舍入操作的模式,用這種模式做舍入操作會(huì)直接拋出異常。

如果你真的需要使用 null 值,但是 null 值不能和 Guava 中的集合實(shí)現(xiàn)一起工作,你只能選擇其他實(shí)現(xiàn)。比如,用 JDK 中的 Collections.unmodifiableList 替代 Guava 的 ImmutableList

Optional

大多數(shù)情況下,開發(fā)人員使用 null 表明的是某種缺失情形:可能是已經(jīng)有一個(gè)默認(rèn)值,或沒有值,或找不到值。例如,Map.get 返回 null 就表示找不到給定鍵對(duì)應(yīng)的值。

Guava 用 Optional表示可能為 null 的 T 類型引用。一個(gè) Optional 實(shí)例可能包含非 null 的引用(我們稱之為引用存在),也可能什么也不包括(稱之為引用缺失)。它從不說包含的是 null 值,而是用存在或缺失來表示。但 Optional 從不會(huì)包含 null 值引用。


    Optional<Integer> possible = Optional.of(5);

    possible.isPresent(); // returns true

    possible.get(); // returns 5

Optional 無意直接模擬其他編程環(huán)境中的”可選” or “可能”語義,但它們的確有相似之處。

Optional 最常用的一些操作被羅列如下:

創(chuàng)建 Optional 實(shí)例(以下都是靜態(tài)方法):

Optional.of(T) 創(chuàng)建指定引用的 Optional 實(shí)例,若引用為 null 則快速失敗
Optional.absent() 創(chuàng)建引用缺失的 Optional 實(shí)例
Optional.fromNullable(T) 創(chuàng)建指定引用的 Optional 實(shí)例,若引用為 null 則表示缺失

用 Optional 實(shí)例查詢引用(以下都是非靜態(tài)方法):

boolean isPresent() 如果 Optional 包含非 null 的引用(引用存在),返回true
T get() 返回 Optional 所包含的引用,若引用缺失,則拋出 java.lang.IllegalStateException
T or(T) 返回 Optional 所包含的引用,若引用缺失,返回指定的值
T orNull() 返回 Optional 所包含的引用,若引用缺失,返回 null
Set<T> asSet() 返回 Optional 所包含引用的單例不可變集,如果引用存在,返回一個(gè)只有單一元素的集合,如果引用缺失,返回一個(gè)空集合。

使用 Optional 的意義在哪兒?

使用 Optional 除了賦予 null 語義,增加了可讀性,最大的優(yōu)點(diǎn)在于它是一種傻瓜式的防護(hù)。Optional 迫使你積極思考引用缺失的情況,因?yàn)槟惚仨氾@式地從 Optional 獲取引用。直接使用 null 很容易讓人忘掉某些情形,盡管 FindBugs 可以幫助查找 null 相關(guān)的問題,但是我們還是認(rèn)為它并不能準(zhǔn)確地定位問題根源。

如同輸入?yún)?shù),方法的返回值也可能是 null。和其他人一樣,你絕對(duì)很可能會(huì)忘記別人寫的方法 method(a,b)會(huì)返回一個(gè) null,就好像當(dāng)你實(shí)現(xiàn) method(a,b)時(shí),也很可能忘記輸入?yún)?shù) a 可以為 null。將方法的返回類型指定為 Optional,也可以迫使調(diào)用者思考返回的引用缺失的情形。

其他處理 null 的便利方法

當(dāng)你需要用一個(gè)默認(rèn)值來替換可能的 null,請(qǐng)使用 Objects.firstNonNull(T, T) 方法。如果兩個(gè)值都是 null,該方法會(huì)拋出 NullPointerException。Optional 也是一個(gè)比較好的替代方案,例如:Optional.of(first).or(second).

還有其它一些方法專門處理 null 或空字符串:emptyToNull(String),nullToEmpty(String),isNullOrEmpty(String)。我們想要強(qiáng)調(diào)的是,這些方法主要用來與混淆 null/空的 API 進(jìn)行交互。當(dāng)每次你寫下混淆 null/空的代碼時(shí),Guava 團(tuán)隊(duì)都淚流滿面。(好的做法是積極地把 null 和空區(qū)分開,以表示不同的含義,在代碼中把 null 和空同等對(duì)待是一種令人不安的壞味道。