Java 14 發佈了,不再怕 NullPointerException 了!

2020年3月17日發佈,Java正式發佈了JDK 14 ,目前已經能夠開放下載。在JDK 14中,共有16個新特性,本文主要來介紹其中的一個特性:JEP 358: Helpful NullPointerExceptionsjava

null何錯之有?

對於Java程序員來講,null是使人頭痛的東西。時常會受到空指針異常(NullPointerException)的騷擾。相信不少程序員都特別懼怕出現程序中出現NPE,由於這種異常每每伴隨着代碼的非預期運行。程序員

在編程語言中,空引用(Null Reference)是一個與空指針相似的概念,是一個已宣告但其並未引用到一個有效對象的變量。編程

在Java 1 中就包含了了Null引用和NPE了,可是其實,Null引用是偉大的計算機科學家Tony Hoare 早在1965年發明的,最初做爲編程語言ALGOL W的一部分。安全

1965年,英國一位名爲Tony Hoare的計算機科學家在設計ALGOL W語言時提出了null引用的想法。ALGOL W是第一批在堆上分配記錄的類型語言之一。Hoare選擇null引用這種方式,「只是由於這種方法實現起來很是容易」。雖然他的設計初衷就是要「經過編譯器的自動檢測機制,確保全部使用引用的地方都是絕對安全的」,他仍是決定爲null引用開個綠燈,由於他認爲這是爲「不存在的值」建模最容易的方式。編程語言

可是在2009年,不少年後,他開始爲本身曾經作過這樣的決定然後悔不已,把它稱爲「一個價值十億美圓的錯誤」。實際上,Hoare的這段話低估了過去五十年來數百萬程序員爲修復空引用所耗費的代價。由於在ALGOL W以後出現的大多數現代程序設計語言,包括Java,都採用了一樣的設計方式,其緣由是爲了與更老的語言保持兼容,或者就像Hoare曾經陳述的那樣,「僅僅是由於這樣實現起來更加容易」。函數

相信不少Java程序員都同樣對null和NPE深惡痛絕,由於他確實會帶來各類各樣的問題(來自《Java 8 實戰》)。如:spa

  • 它是錯誤之源。 NullPointerException是目前Java程序開發中最典型的異常。它會使你的代碼膨脹。
  • 它讓你的代碼充斥着深度嵌套的null檢查,代碼的可讀性糟糕透頂。
  • 它自身是毫無心義的。 null自身沒有任何的語義,尤爲是是它表明的是在靜態類型語言中以一種錯誤的方式對缺失變量值的建模。
  • 它破壞了Java的哲學。 Java一直試圖避免讓程序員意識到指針的存在,惟一的例外是:null指針。
  • 它在Java的類型系統上開了個口子。 null並不屬於任何類型,這意味着它能夠被賦值給任意引用類型的變量。這會致使問題, 緣由是當這個變量被傳遞到系統中的另外一個部分後,你將沒法獲知這個null變量最初賦值究竟是什麼類型。

其餘語言如何解決NPE問題

咱們知道,出了Java語言外,還有不少其餘的面嚮對象語言,那麼在其餘的一些語言中,是如何解決NPE的問題的呢?.net

如在Groovy中使用安全導航操做符(Safe Navigation Operator)能夠訪問可能爲null的變量:設計

def carInsuranceName = person?.car?.insurance?.name
複製代碼

Groovy的安全導航操做符可以避免在訪問這些可能爲null引用的變量時發生NullPointerException,在調用鏈中的變量遭遇null時將null引用沿着調用鏈傳遞下去,返回一個null。指針

其實這個功能曾經考慮過增長一個相似的功能,可是後來又被捨棄了。

另外,在Haskell和Scala也有相似的替代品,如Haskell中的Maybe類型、Scala中的Option[T]。

在 Kotlin 中,其類型系統嚴格區分一個引用能夠容納 null 仍是不能容納。也就是說,一個變量是否可空必須顯示聲明,對於可空變量,在訪問其成員時必須作空處理,不然沒法編譯經過:

var a: String = "abc"
a = null // 編譯錯誤
複製代碼

果容許爲空,能夠聲明一個可空字符串,寫做 String?:

var b: String? = "abc" //String? 表示該 String 類型變量可爲空
b = null // 編譯經過
複製代碼

看到這個?的時候,是否是發現和Groovy有點像?不過仍是有必定區別的,這裏就不展開了。

好了,書歸正傳,咱們來看看做爲一個TOIBE編程語言排行榜第一名的語言,Java語言對於NPE作出了哪些努力!

Java作了哪些努力

一直以來對於null和NPE的改進仍是作出了一些努力的。

首先在Java 8中提供了Optional,其實在Java 8 推出以前,Google的Guava庫中就率先提供過Optional接口來使null快速失敗。

Optional在可能爲null的對象上作了一層封裝,Optional對象包含了一些方法來顯式地處理某個值是存在仍是缺失,Optional類強制你思考值不存在的狀況,這樣就能避免潛在的空指針異常。

可是設計Optional類的目的並非徹底取代null,它的目的是設計更易理解的API。經過Optional,能夠從方法簽名就知道這個函數有可能返回一個缺失的值,這樣強制你處理這些缺失值的狀況。

關於Optional的用法,不是本文的重點,就不在這裏詳細介紹了,筆者在平常開發中常常結合Stream一塊兒使用Optional,仍是比較好用的。

另一個值得一提的就是最近(2020年03月17日)發佈的JDK 14中對於NPE有了一個加強。那就是JEP 358: Helpful NullPointerExceptions

更有幫助的NPE

JDK 14中對於NEP有了一個加強,既然NPE暫時沒法避免,那麼就讓他對開發者更有幫助一些。

-w602

每一個Java開發人員都遇到過NullPointerExceptions (NPEs)。因爲NPEs能夠發生在程序的幾乎任何地方,試圖捕獲並從它們中恢復一般是不切實際的。所以,開發人員一般依賴於JVM來肯定NPE實際發生時的來源。例如,假設在這段代碼中出現了一個NPE:

a.i = 99;
複製代碼

JVM將打印出致使NPE的方法、文件名和行號:

Exception in thread "main" java.lang.NullPointerException
at Prog.main(Prog.java:5)
複製代碼

經過以上堆棧信息,開發人員能夠定位到a.i= 99這一行,並推斷出a必定是null。

可是,對於更復雜的代碼,若是不使用調試器,就不可能肯定哪一個變量是null。假設在這段代碼中出現了一個NPE:

a.b.c.i = 99;
複製代碼

咱們根本沒法肯定究竟是a仍是b或者是c在運行時是個null值。

可是,在JDK14之後,這種窘境就有解了。

在JDK14中,當運行期,試圖對一個null對象進行應用時,JVM依然會拋出一個NullPointerException (NPE),除此以外,還會經過經過分析程序的字節碼指令,JVM將精確地肯定哪一個變量是null,而且在堆棧信息中明確的提示出來。

在JDK 14中,若是上文中的a.i = 99發生NPE,將會打印以下堆棧:

Exception in thread "main" java.lang.NullPointerException: 
        Cannot assign field "i" because "a" is null
    at Prog.main(Prog.java:5)
複製代碼

若是是a.b.c.i = 99;中的b爲null致使了空指針,則會打印如下堆棧信息:

Exception in thread "main" java.lang.NullPointerException: 
        Cannot read field "c" because "a.b" is null
    at Prog.main(Prog.java:5)
複製代碼

可見,堆棧中明確指出了究竟是哪一個對象爲null而致使了NPE,這樣,一旦應用中發生NPE,開發者能夠經過堆棧信息第一時間定位到究竟是代碼中的那個對象爲null致使的。

這算是JDK的一個小小的改進,可是這個改進對於開發者來講確實是很是友好的。真的但願這些小而美的改動能夠在JDK中愈來愈多。

參考資料:

openjdk.java.net/jeps/358

《Java 8 In Action》

相關文章
相關標籤/搜索