| ? ? ? ? 輕率地使用null可能會(huì)導(dǎo)致很多令人驚愕的問(wèn)題。通過(guò)學(xué)習(xí)Google底層代碼庫(kù),我們發(fā)現(xiàn)95%的集合類不接受null值作為元素。我們認(rèn)為, 相比默默地接受null,使用快速失敗操作拒絕null值對(duì)開(kāi)發(fā)者更有幫助。 此外,Null的含糊語(yǔ)義讓人很不舒服。Null很少可以明確地表示某種語(yǔ)義,例如,Map.get(key)返回Null時(shí),可能表示map中的值是null,亦或map中沒(méi)有key對(duì)應(yīng)的值。Null可以表示失敗、成功或幾乎任何情況。使用Null以外的特定值,會(huì)讓你的邏輯描述變得更清晰。 Null確實(shí)也有合適和正確的使用場(chǎng)景,如在性能和速度方面Null是廉價(jià)的,而且在對(duì)象數(shù)組中,出現(xiàn)Null也是無(wú)法避免的。但相對(duì)于底層庫(kù)來(lái)說(shuō),在應(yīng)用級(jí)別的代碼中,Null往往是導(dǎo)致混亂,疑難問(wèn)題和模糊語(yǔ)義的元兇,就如同我們舉過(guò)的Map.get(key)的例子。最關(guān)鍵的是,Null本身沒(méi)有定義它表達(dá)的意思。 鑒于這些原因,很多Guava工具類對(duì)Null值都采用快速失敗操作,除非工具類本身提供了針對(duì)Null值的因變措施。此外,Guava還提供了很多工具類,讓你更方便地用特定值替換Null值。 具體案例 不要在Set中使用null,或者把null作為map的鍵值。使用特殊值代表null會(huì)讓查找操作的語(yǔ)義更清晰。 如果你想把null作為map中某條目的值,更好的辦法是 不把這一條目放到map中,而是單獨(dú)維護(hù)一個(gè)”值為null的鍵集合” (null keys)。Map 中對(duì)應(yīng)某個(gè)鍵的值是null,和map中沒(méi)有對(duì)應(yīng)某個(gè)鍵的值,是非常容易混淆的兩種情況。因此,最好把值為null的鍵分離開(kāi),并且仔細(xì)想想,null值的鍵在你的項(xiàng)目中到底表達(dá)了什么語(yǔ)義。 如果你需要在列表中使用null——并且這個(gè)列表的數(shù)據(jù)是稀疏的,使用Map<Integer, E>可能會(huì)更高效,并且更準(zhǔn)確地符合你的潛在需求。 此外,考慮一下使用自然的null對(duì)象——特殊值。舉例來(lái)說(shuō),為某個(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ù)情況下,開(kāi)發(fā)人員使用null表明的是某種缺失情形:可能是已經(jīng)有一個(gè)默認(rèn)值,或沒(méi)有值,或找不到值。例如,Map.get返回null就表示找不到給定鍵對(duì)應(yīng)的值。 Guava用Optional<T>表示可能為null的T類型引用。一個(gè)Optional實(shí)例可能包含非null的引用(我們稱之為引用存在),也可能什么也不包括(稱之為引用缺失)。它從不說(shuō)包含的是null值,而是用存在或缺失來(lái)表示。但Optional從不會(huì)包含null值引用。 | 1 | Optional<Integer> possible = Optional.of(5); |
| 3 | possible.isPresent();?// returns true |
| 5 | possible.get();?// returns 5 |
Optional無(wú)意直接模擬其他編程環(huán)境中的”可選” or “可能”語(yǔ)義,但它們的確有相似之處。 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ǔ)義,增加了可讀性,最大的優(yōu)點(diǎn)在于它是一種傻瓜式的防護(hù)。Optional迫使你積極思考引用缺失的情況,因?yàn)槟惚仨氾@式地從Optional獲取引用。直接使用null很容易讓人忘掉某些情形,盡管FindBugs可以幫助查找null相關(guān)的問(wèn)題,但是我們還是認(rèn)為它并不能準(zhǔn)確地定位問(wè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)值來(lái)替換可能的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)的是,這些方法主要用來(lái)與混淆null/空的API進(jìn)行交互。當(dāng)每次你寫下混淆null/空的代碼時(shí),Guava團(tuán)隊(duì)都淚流滿面。(好的做法是積極地把null和空區(qū)分開(kāi),以表示不同的含義,在代碼中把null和空同等對(duì)待是一種令人不安的壞味道。 |