Java 14 发布了,再也不怕NullPointerException 了!?
△Hollis, 一個(gè)對(duì)Coding有著獨(dú)特追求的人△
這是Hollis的第?258篇原創(chuàng)分享
作者 l Hollis
來(lái)源 l Java之道(ID:javaways)
2020年3月17日發(fā)布,Java正式發(fā)布了JDK 14 ,目前已經(jīng)可以開放下載。在JDK 14中,共有16個(gè)新特性,本文主要來(lái)介紹其中的一個(gè)特性:JEP 358: Helpful NullPointerExceptions
null何錯(cuò)之有?
對(duì)于Java程序員來(lái)說(shuō),null是令人頭痛的東西。時(shí)常會(huì)受到空指針異常(NullPointerException)的騷擾。相信很多程序員都特別害怕出現(xiàn)程序中出現(xiàn)NPE,因?yàn)檫@種異常往往伴隨著代碼的非預(yù)期運(yùn)行。
在Java 1 中就包含了了Null引用和NPE了,但是其實(shí),Null引用是偉大的計(jì)算機(jī)科學(xué)家Tony Hoare 早在1965年發(fā)明的,最初作為編程語(yǔ)言ALGOL W的一部分。
1965年,英國(guó)一位名為Tony Hoare的計(jì)算機(jī)科學(xué)家在設(shè)計(jì)ALGOL W語(yǔ)言時(shí)提出了null引用的想法。ALGOL W是第一批在堆上分配記錄的類型語(yǔ)言之一。Hoare選擇null引用這種方式,“只是因?yàn)檫@種方法實(shí)現(xiàn)起來(lái)非常容易”。雖然他的設(shè)計(jì)初衷就是要“通過(guò)編譯器的自動(dòng)檢測(cè)機(jī)制,確保所有使用引用的地方都是絕對(duì)安全的”,他還是決定為null引用開個(gè)綠燈,因?yàn)樗J(rèn)為這是為“不存在的值”建模最容易的方式。
但是在2009年,很多年后,他開始為自己曾經(jīng)做過(guò)這樣的決定而后悔不已,把它稱為“一個(gè)價(jià)值十億美元的錯(cuò)誤”。
實(shí)際上,Hoare的這段話低估了過(guò)去五十年來(lái)數(shù)百萬(wàn)程序員為修復(fù)空引用所耗費(fèi)的代價(jià)。因?yàn)樵贏LGOL W之后出現(xiàn)的大多數(shù)現(xiàn)代程序設(shè)計(jì)語(yǔ)言,包括Java,都采用了同樣的設(shè)計(jì)方式,其原因是為了與更老的語(yǔ)言保持兼容,或者就像Hoare曾經(jīng)陳述的那樣,“僅僅是因?yàn)檫@樣實(shí)現(xiàn)起來(lái)更加容易”。
相信很多Java程序員都一樣對(duì)null和NPE深惡痛絕,因?yàn)樗_實(shí)會(huì)帶來(lái)各種各樣的問(wèn)題(來(lái)自《Java 8 實(shí)戰(zhàn)》)。如:
它是錯(cuò)誤之源。NullPointerException是目前Java程序開發(fā)中最典型的異常。它會(huì)使你的代碼膨脹。
它讓你的代碼充斥著深度嵌套的null檢查,代碼的可讀性糟糕透頂。
它自身是毫無(wú)意義的。null自身沒(méi)有任何的語(yǔ)義,尤其是是它代表的是在靜態(tài)類型語(yǔ)言中以一種錯(cuò)誤的方式對(duì)缺失變量值的建模。
它破壞了Java的哲學(xué)。Java一直試圖避免讓程序員意識(shí)到指針的存在,唯一的例外是:null指針。
它在Java的類型系統(tǒng)上開了個(gè)口子。null并不屬于任何類型,這意味著它可以被賦值給任意引用類型的變量。這會(huì)導(dǎo)致問(wèn)題, 原因是當(dāng)這個(gè)變量被傳遞到系統(tǒng)中的另一個(gè)部分后,你將無(wú)法獲知這個(gè)null變量最初賦值到底是什么類型。
其他語(yǔ)言如何解決NPE問(wèn)題
我們知道,出了Java語(yǔ)言外,還有很多其他的面向?qū)ο笳Z(yǔ)言,那么在其他的一些語(yǔ)言中,是如何解決NPE的問(wèn)題的呢?
如在Groovy中使用安全導(dǎo)航操作符(Safe Navigation Operator)可以訪問(wèn)可能為null的變量:
def carInsuranceName = person?.car?.insurance?.name
Groovy的安全導(dǎo)航操作符能夠避免在訪問(wèn)這些可能為null引用的變量時(shí)發(fā)生NullPointerException,在調(diào)用鏈中的變量遭遇null時(shí)將null引用沿著調(diào)用鏈傳遞下去,返回一個(gè)null。
其實(shí)這個(gè)功能曾經(jīng)考慮過(guò)增加一個(gè)類似的功能,但是后來(lái)又被舍棄了。
另外,在Haskell和Scala也有類似的替代品,如Haskell中的Maybe類型、Scala中的Option[T]。
在 Kotlin 中,其類型系統(tǒng)嚴(yán)格區(qū)分一個(gè)引用可以容納 null 還是不能容納。也就是說(shuō),一個(gè)變量是否可空必須顯示聲明,對(duì)于可空變量,在訪問(wèn)其成員時(shí)必須做空處理,否則無(wú)法編譯通過(guò):
var a: String = "abc" a = null // 編譯錯(cuò)誤果允許為空,可以聲明一個(gè)可空字符串,寫作 String?:
var b: String? = "abc" //String? 表示該 String 類型變量可為空 b = null // 編譯通過(guò)看到這個(gè)?的時(shí)候,是不是發(fā)現(xiàn)和Groovy有點(diǎn)像?不過(guò)還是有一定區(qū)別的,這里就不展開了。
好了,書歸正傳,我們來(lái)看看作為一個(gè)TOIBE編程語(yǔ)言排行榜第一名的語(yǔ)言,Java語(yǔ)言對(duì)于NPE做出了哪些努力!
Java做了哪些努力
一直以來(lái)對(duì)于null和NPE的改進(jìn)還是做出了一些努力的。
首先在Java 8中提供了Optional,其實(shí)在Java 8 推出之前,Google的Guava庫(kù)中就率先提供過(guò)Optional接口來(lái)使null快速失敗。
Optional在可能為null的對(duì)象上做了一層封裝,Optional對(duì)象包含了一些方法來(lái)顯式地處理某個(gè)值是存在還是缺失,Optional類強(qiáng)制你思考值不存在的情況,這樣就能避免潛在的空指針異常。
但是設(shè)計(jì)Optional類的目的并不是完全取代null,它的目的是設(shè)計(jì)更易理解的API。通過(guò)Optional,可以從方法簽名就知道這個(gè)函數(shù)有可能返回一個(gè)缺失的值,這樣強(qiáng)制你處理這些缺失值的情況。
關(guān)于Optional的用法,不是本文的重點(diǎn),就不在這里詳細(xì)介紹了,筆者在日常開發(fā)中經(jīng)常結(jié)合Stream一起使用Optional,還是比較好用的。
另外一個(gè)值得一提的就是最近(2020年03月17日)發(fā)布的JDK 14中對(duì)于NPE有了一個(gè)增強(qiáng)。那就是JEP 358: Helpful NullPointerExceptions
更有幫助的NPE
JDK 14中對(duì)于NEP有了一個(gè)增強(qiáng),既然NPE暫時(shí)無(wú)法避免,那么就讓他對(duì)開發(fā)者更有幫助一些。
每個(gè)Java開發(fā)人員都遇到過(guò)NullPointerException (NPE)。由于NPE可以發(fā)生在程序的幾乎任何地方,試圖捕獲并從它們中恢復(fù)通常是不切實(shí)際的。因此,開發(fā)人員通常依賴于JVM來(lái)確定NPE實(shí)際發(fā)生時(shí)的來(lái)源。例如,假設(shè)在這段代碼中出現(xiàn)了一個(gè)NPE:
a.i = 99;JVM將打印出導(dǎo)致NPE的方法、文件名和行號(hào):
?
Exception in thread "main" java.lang.NullPointerException at Prog.main(Prog.java:5)通過(guò)以上堆棧信息,開發(fā)人員可以定位到a.i= 99這一行,并推斷出a一定是null。
但是,對(duì)于更復(fù)雜的代碼,如果不使用調(diào)試器,就不可能確定哪個(gè)變量是null。假設(shè)在這段代碼中出現(xiàn)了一個(gè)NPE:
?
a.b.c.i = 99;我們根本無(wú)法確定到底是a還是b或者是c在運(yùn)行時(shí)是個(gè)null值。
但是,在JDK14以后,這種窘境就有解了。
在JDK14中,當(dāng)運(yùn)行期,試圖對(duì)一個(gè)bull對(duì)象進(jìn)行應(yīng)用時(shí),JVM依然會(huì)拋出一個(gè)NullPointerException (NPE),除此之外,還會(huì)通過(guò)通過(guò)分析程序的字節(jié)碼指令,JVM將精確地確定哪個(gè)變量是null,并且在堆棧信息中明確的提示出來(lái)。
在JDK 14中,如果上文中的a.i = 99發(fā)生NPE,將會(huì)打印如下堆棧:
?
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導(dǎo)致了空指針,則會(huì)打印以下堆棧信息:
Exception in thread "main" java.lang.NullPointerException: ? Cannot read field "c" because "a.b" is null ? at Prog.main(Prog.java:5)可見(jiàn),堆棧中明確指出了到底是哪個(gè)對(duì)象為null而導(dǎo)致了NPE,這樣,一旦應(yīng)用中發(fā)生NPE,開發(fā)者可以通過(guò)堆棧信息第一時(shí)間定位到到底是代碼中的那個(gè)對(duì)象為null導(dǎo)致的。
這算是JDK的一個(gè)小小的改進(jìn),但是這個(gè)改進(jìn)對(duì)于開發(fā)者來(lái)說(shuō)確實(shí)是非常友好的。真的希望這些小而美的改動(dòng)可以在JDK中越來(lái)越多。
參考資料:
https://openjdk.java.net/jeps/358
《Java 8 In Action》
關(guān)于作者:Hollis,一個(gè)對(duì)Coding有著獨(dú)特追求的人,現(xiàn)任阿里巴巴技術(shù)專家,個(gè)人技術(shù)博主,技術(shù)文章全網(wǎng)閱讀量數(shù)千萬(wàn),《程序員的三門課》聯(lián)合作者。
Java工程師成神之路系列文章在 GitHub 更新中,歡迎關(guān)注,歡迎star。?直面Java第305期:TLAB帶來(lái)的問(wèn)題?深入并發(fā)第013期:拓展synchronized——鎖優(yōu)化 - MORE | 更多精彩文章 -互聯(lián)網(wǎng)公司沒(méi)有中年人 聽(tīng)說(shuō)你 ping 用的很 6 ?給我圖解一下 ping 的工作原理! 一份來(lái)自亞馬遜工程師的Google面試指南,GitHub收獲9.8萬(wàn)星,已翻譯成中文 能力不錯(cuò)的某大廠高P,竟然折在了一個(gè)小廠的試用期上! 如果你喜歡本文,請(qǐng)長(zhǎng)按二維碼,關(guān)注?Hollis.轉(zhuǎn)發(fā)至朋友圈,是對(duì)我最大的支持。好文章,我在看??總結(jié)
以上是生活随笔為你收集整理的Java 14 发布了,再也不怕NullPointerException 了!?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 面试官真是搞笑!让实现线程安全的单例,又
- 下一篇: NYOJ 420 p次方求和 大数