要說 Java 編程中哪個異常是你印象最深刻的,那 NullPointerException 空指針可以說是臭名昭著的考阱。不要說初級程序員會碰到翠忠, 即使是中級,專家級程序員稍不留神乞榨,就會掉入這個坑里秽之。
Null 引用的發(fā)明者 Tony Hoare 曾在 2009 年作出道歉聲明,聲明中表示吃既,到目前為止考榨,空指針異常大約給企業(yè)已造成數(shù)十億美元的損失。
下面是 Tony Hoare 的原話:
我將 Null 引用的設(shè)計(jì)稱為是一個數(shù)十億美元的錯誤鹦倚。1965 那年河质,我正在用面向?qū)ο笳Z言(ALGOL W) 設(shè)計(jì)首個功能全面的系統(tǒng)。當(dāng)時我的考量是震叙,確保所有被使用的引用都是安全的掀鹅,編譯器會自動進(jìn)行檢查。但是媒楼,我沒有抵住誘惑乐尊,加入了 Null 引用,僅僅是為了實(shí)現(xiàn)起來省事划址。這之后科吭,它導(dǎo)致了數(shù)不清的 bug昏滴、錯誤和系統(tǒng)崩潰猴鲫,也為企業(yè)導(dǎo)致了不可估量的損失对人。
事已至此,我們必須學(xué)會面對它拂共。So, 我們要如何防止空指針異常呢牺弄?
唯一的辦法就是對可能為 Null 的對象添加檢查。但是 Null 檢查是繁瑣且痛苦的宜狐。所以一些比較新的語言為了處理 Null 檢查势告,特意添加了特殊的語法,如空合并運(yùn)算符抚恒。
在 Groovy 或 Kotlin 這樣的語言中也被稱為 Elvis 運(yùn)算符咱台。
不幸的是,在老版本的 Java 中并沒有提供這樣的語法糖俭驮。Java8 中在這方面做了改進(jìn)回溺。所以,這篇文章就特意來介紹一下如何在 Java8 中利用新特性來編寫防止 NullPointerException的發(fā)生混萝。
Java8 中如何加強(qiáng)對 Null 對象的檢查遗遵?
在業(yè)務(wù)系統(tǒng)中,對象中嵌套對象是經(jīng)常發(fā)生的場景逸嘀,如下示例代碼:
// 最外層對象
class Outer {
Nested nested;
Nested getNested() {
return nested;
}
}
// 第二層對象
class Nested {
Inner inner;
Inner getInner() {
return inner;
}
}
// 最底層對象
class Inner {
String foo;
String getFoo() {
return foo;
}
}
業(yè)務(wù)中车要,假設(shè)我們需要獲取 Outer 對象對底層的 Inner 中的 foo 屬性,我們必須寫一堆的非空校驗(yàn)崭倘,來防止發(fā)生 NullPointerException:
// 繁瑣的代碼
Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
System.out.println(outer.nested.inner.foo);
}
通過 Optional
在 Java8 中翼岁,我們有更優(yōu)雅的解決方式,那就是使用 Optional是說司光,我們可以在一行代碼中琅坡,進(jìn)行流水式的 map 操作。而 map 方法內(nèi)部會自動進(jìn)行空校驗(yàn):
Optional.of(new Outer())
.map(Outer::getNested)
.map(Nested::getInner)
.map(Inner::getFoo
.ifPresent(System.out::println); // 如果不為空飘庄,最終輸出 foo 的值
通過 suppiler 函數(shù)自定義增強(qiáng) API
上面這種方式個人感覺還是有點(diǎn)啰嗦脑蠕,我們可以利用 suppiler 函數(shù)來出一個終極解決方案:
public static Optional resolve(Supplier resolver) {
try {
T result = resolver.get();
return Optional.ofNullable(result);
}
catch (NullPointerException e) {
// 可能會拋出空指針異常,直接返回一個空的 Optional 對象
return Optional.empty();
}
}
利用上面的 resolve 方法來重構(gòu)上述的非空校驗(yàn)代碼段:
Outer obj = new Outer();
// 直接調(diào)用 resolve 方法跪削,內(nèi)部做空指針的處理
resolve(() -> obj.getNested().getInner().getFoo());
.ifPresent(System.out::println); // 如果不為空谴仙,最終輸出 foo 的值
總結(jié)
你需要知道的是,上面這兩個解決方案并沒傳統(tǒng)的 null 檢查性能那么高效碾盐。但在絕大部分業(yè)務(wù)場景下晃跺,舍棄那么一丟丟的性能來方便編碼,是完全可取毫玖, 除非是那種對性能有嚴(yán)格要求的場景掀虎,我們才不建議使用凌盯。
個人覺得,真要拿這點(diǎn)性能說事烹玉,還不如去優(yōu)化優(yōu)化 sql 語句驰怎,業(yè)務(wù)邏輯等