NullPointerException
是當(dāng)您嘗試使用指向內(nèi)存中任何位置 (null) 的引用時發(fā)生的異常,就像引用對象一樣。對空引用調(diào)用方法或嘗試訪問空引用的字段將觸發(fā) NullPointerException
。這些是最常見的,但 中列出了其他方法NullPointerException
javadoc 頁面。
我能想到的用來說明 NullPointerException 的最快示例代碼可能是:
public class Example { public static void main(String[] args) { Object obj = null; obj.hashCode(); } }
在 main
內(nèi)的第一行,我顯式地將 Object
引用 obj
設(shè)置為 null
。這意味著我有一個引用,但它沒有指向任何對象。之后,我嘗試通過調(diào)用該引用的方法來將該引用視為指向一個對象。這會導(dǎo)致 NullPointerException
,因?yàn)樵谝弥赶虻奈恢脹]有要執(zhí)行的代碼。
(這是一個技術(shù)問題,但我認(rèn)為值得一提的是:指向 null 的引用與指向無效內(nèi)存位置的 C 指針不同。空指針實(shí)際上不指向任何地方,這與指向一個恰好無效的位置有細(xì)微的不同。)
Java 中有兩種主要類型的變量:
基元:包含數(shù)據(jù)的變量。如果您想操作原始變量中的數(shù)據(jù),您可以直接操作該變量。按照慣例,原始類型以小寫字母開頭。例如,int
或 char
類型的變量是基元。
引用:包含對象
內(nèi)存地址的變量,即引用對象的變量代碼>.如果您想要操作引用變量引用的Object
,則必須取消引用它。取消引用通常需要使用 .
訪問方法或字段,或使用 [
索引數(shù)組。按照慣例,引用類型通常用以大寫字母開頭的類型來表示。例如,Object
類型的變量是引用。
考慮以下代碼,您在其中聲明 int
類型的原始變量,但不初始化它:
int x; int y = x + x;
這兩行會使程序崩潰,因?yàn)闆]有為 x
指定任何值,而我們正在嘗試使用 x
的值來指定 y
>。所有基元在被操作之前都必須初始化為可用值。
現(xiàn)在事情變得有趣了。 引用變量可以設(shè)置為null
,這意味著“我沒有引用任何東西”。如果您以這種方式顯式設(shè)置引用變量,則可以在引用變量中獲取 null
值,或者引用變量未初始化并且編譯器不會捕獲它(Java 會自動將該變量設(shè)置為 null
)。
如果您顯式地或通過 Java 自動將引用變量設(shè)置為 null,并且您嘗試取消引用它,您將得到一個 NullPointerException
。
當(dāng)您聲明一個變量但在嘗試使用該變量的內(nèi)容之前沒有創(chuàng)建對象并將其分配給該變量時,通常會發(fā)生NullPointerException
(NPE)。所以你引用了一些實(shí)際上并不存在的東西。
采用以下代碼:
Integer num; num = new Integer(10);
第一行聲明了一個名為num
的變量,但它實(shí)際上還不包含引用值。由于您還沒有說出要指向什么,Java 將其設(shè)置為 null
。
第二行,new
關(guān)鍵字用于實(shí)例化(或創(chuàng)建)一個Integer
類型的對象,引用變量num
被分配給該 Integer
對象。
如果您在創(chuàng)建對象之前嘗試取消引用num
,,您將得到一個NullPointerException
。在最簡單的情況下,編譯器會捕獲問題并讓您知道“num 可能尚未初始化
”,但有時您可能會編寫不直接創(chuàng)建對象的代碼。
例如,您可能有如下方法:
public void doSomething(SomeObject obj) { // Do something to obj, assumes obj is not null obj.myMethod(); }
在這種情況下,您不會創(chuàng)建對象 obj
,而是假設(shè)它是在調(diào)用 doSomething()
方法之前創(chuàng)建的。請注意,可以像這樣調(diào)用該方法:
doSomething(null);
在這種情況下,obj
為 null
,并且語句 obj.myMethod()
將拋出 NullPointerException
>.
如果該方法打算像上面的方法那樣對傳入的對象執(zhí)行某些操作,則拋出 NullPointerException
是適當(dāng)?shù)?,因?yàn)檫@是程序員錯誤,并且程序員需要該信息調(diào)試目的。
除了由于方法邏輯引發(fā)的 NullPointerException
異常之外,您還可以檢查方法參數(shù)中的 null
值,并通過添加類似以下內(nèi)容來顯式拋出 NPE:在方法開頭附近跟隨:
// Throws an NPE with a custom error message if obj is null Objects.requireNonNull(obj, "obj must not be null");
請注意,在錯誤消息中明確說明哪個對象不能為null
會很有幫助。驗(yàn)證這一點(diǎn)的優(yōu)點(diǎn)是 1) 您可以返回自己更清晰的錯誤消息,2) 對于方法的其余部分,您知道除非重新分配 obj ,否則它不為 null 并且可以安全地取消引用.
或者,在某些情況下,該方法的目的不僅僅是對傳入的對象進(jìn)行操作,因此空參數(shù)可能是可接受的。在這種情況下,您需要檢查空參數(shù)并采取不同的行為。您還應(yīng)該在文檔中對此進(jìn)行解釋。例如,doSomething()
可以寫為:
/** * @param obj An optional foo for ____. May be null, in which case * the result will be ____. */ public void doSomething(SomeObject obj) { if(obj == null) { // Do something } else { // Do something else } }
具有查找錯誤功能的聲納可以檢測 NPE。 sonar能否動態(tài)捕獲JVM引起的空指針異常一個>
現(xiàn)在 Java 14 添加了一項(xiàng)新的語言功能來顯示 NullPointerException 的根本原因。自 2006 年以來,此語言功能已成為 SAP 商業(yè) JVM 的一部分。
在 Java 14 中,以下是 NullPointerException 異常消息示例:
NullPointerException
發(fā)生的情況列表以下是 Java 語言規(guī)范直接*提到的發(fā)生 NullPointerException
的所有情況:
拋出空值;
synchronized (someNullReference) { ... }
NullPointerException
NullPointerException
。super
會引發(fā) NullPointerException
。如果您感到困惑,這是在談?wù)摵细竦某悩?gòu)造函數(shù)調(diào)用:class Outer { class Inner {} } class ChildOfInner extends Outer.Inner { ChildOfInner(Outer o) { o.super(); // if o is null, NPE gets thrown } }
使用 for (element : iterable)
循環(huán)來循環(huán)遍歷空集合/數(shù)組。
switch (foo) { ... }
(無論是表達(dá)式還是語句)在 foo
時可以拋出 NullPointerException
為空。
foo.new SomeInnerClass()
當(dāng) foo
為 null 時拋出 NullPointerException
。
name1::name2
或 primaryExpression::name
形式的方法引用在以下情況下求值時會拋出 NullPointerException
name1 或 primaryExpression
計(jì)算結(jié)果為 null。
來自 JLS 的注釋指出,someInstance.someStaticMethod()
不會拋出 NPE,因?yàn)?someStaticMethod
是靜態(tài)的,但 someInstance:: someStaticMethod
仍然拋出 NPE!
* 請注意,JLS 可能還間接談?wù)摿撕芏嘤嘘P(guān) NPE 的內(nèi)容。