如何在 Java8 中風騷走位避開空指針異常
- 2019 年 10 月 6 日
- 筆記

要說 Java 編程中哪個異常是你印象最深刻的,那 NullPointerException
空指針可以說是臭名昭著的。不要說初級程序員會碰到,即使是中級,專家級程序員稍不留神,就會掉入這個坑裡。
Null
引用的發明者 Tony Hoare 曾在 2009 年作出道歉聲明,聲明中表示,到目前為止,空指針異常大約給企業已造成數十億美元的損失。
下面是 Tony Hoare 的原話:
我將 Null 引用的設計稱為是一個數十億美元的錯誤。1965 那年,我正在用面向對象語言(ALGOL W) 設計首個功能全面的系統。當時我的考量是,確保所有被使用的引用都是安全的,編譯器會自動進行檢查。但是,我沒有抵住誘惑,加入了 Null 引用,僅僅是為了實現起來省事。這之後,它導致了數不清的 bug、錯誤和系統崩潰,也為企業導致了不可估量的損失。
事已至此,我們必須學會面對它。So, 我們要如何防止空指針異常呢?
唯一的辦法就是對可能為 Null 的對象添加檢查。但是 Null 檢查是繁瑣且痛苦的。所以一些比較新的語言為了處理 Null 檢查,特意添加了特殊的語法,如空合併運算符。
在 Groovy 或 Kotlin 這樣的語言中也被稱為
Elvis
運算符。
不幸的是,在老版本的 Java 中並沒有提供這樣的語法糖。Java8 中在這方面做了改進。所以,這篇文章就特意來介紹一下如何在 Java8 中利用新特性來編寫防止 NullPointerException
的發生。
Java8 中如何加強對 Null 對象的檢查?
在上篇文章 Java8 新特性指導手冊 中簡單的提了一下如何通過 Optional
類來對對象做空校驗。接下來,我們再細說一下:

在業務系統中,對象中嵌套對象是經常發生的場景,如下示例代碼:
// 最外層對象 class Outer { Nested nested; Nested getNested() { return nested; } } // 第二層對象 class Nested { Inner inner; Inner getInner() { return inner; } } // 最底層對象 class Inner { String foo; String getFoo() { return foo; } }
業務中,假設我們需要獲取 Outer
對象對底層的 Inner
中的 foo
屬性,我們必須寫一堆的非空校驗,來防止發生 NullPointerException
:
// 繁瑣的代碼 Outer outer = new Outer(); if (outer != null && outer.nested != null && outer.nested.inner != null) { System.out.println(outer.nested.inner.foo); }
通過 Optional
在 Java8 中,我們有更優雅的解決方式,那就是使用 Optional
是說,我們可以在一行代碼中,進行流水式的 map
操作。而 map 方法內部會自動進行空校驗:
Optional.of(new Outer()) .map(Outer::getNested) .map(Nested::getInner) .map(Inner::getFoo .ifPresent(System.out::println); // 如果不為空,最終輸出 foo 的值
通過 suppiler 函數自定義方法
上面這種方式個人感覺還是有點啰嗦,我們可以利用 suppiler
函數來出一個終極解決方案:
public static <T> Optional<T> resolve(Supplier<T> resolver) { try { T result = resolver.get(); return Optional.ofNullable(result); } catch (NullPointerException e) { // 可能會拋出空指針異常,直接返回一個空的 Optional 對象 return Optional.empty(); } }
利用上面的 resolve
方法來重構上述的非空校驗代碼段:
Outer obj = new Outer(); // 直接調用 resolve 方法,內部做空指針的處理 resolve(() -> obj.getNested().getInner().getFoo()); .ifPresent(System.out::println); // 如果不為空,最終輸出 foo 的值
最後
你需要知道的是,上面這兩個解決方案並沒傳統的 null
檢查性能那麼高效。但在絕大部分業務場景下,捨棄那麼一丟丟的性能來方便編碼,是完全可取,除非是那種對性能有嚴格要求的,我們才不建議使用。
個人覺得,真要拿這點性能說事,還不如去優化優化
sql
語句,業務邏輯等。