天天看點

什麼是JAVA中的NullPointerExceptions,如何避免?

作者:小朝的程式設計生涯

什麼是JAVA中的空指針(NullPointerExceptions),如何避免程式中的空指針?

空指針異常可以說是java中最常見的異常之一了,你對它又了解多少呢?

什麼是空指針

衆所周知,JAVA中有兩大類類型:基本類型和引用類型。基本類型可以由編譯器保證使用前必須初始化為一個有效值(但是不一定是業務的有效值),使用它不會導緻空指針異常;而引用類型可以被初始化為一個特殊的值null,說明該引用未引用任何值。

當使用一個null對象的屬性,或調用其方法時,會觸發空指針異常(NullPointerExceptions)。下面是官方文檔給出的抛出異常的情況:

  • Calling the instance method of a null object.(調用null的執行個體方法)
  • Accessing or modifying the field of a null object.(調用或者修改null的屬性)
  • Taking the length of null as if it were an array.(擷取null數組的長度)
  • Accessing or modifying the slots of null as if it were an array.(通路或修改null數組中的元素)
  • Throwing null as if it were a Throwable value.(正常抛出null異常)

調用null的執行個體方法 / 調用或者修改null的屬性

可以看下面的代碼示例:

// 類型定義
class SomeClass{
    // 僅作示範,盡量不要定義public的屬性。
    public int someField;
    public void someMethod(){
    }
}

SomeClass someClass = null;
someClass.someMethod();
someClass.someField = 10;
           

輸出:

Exception in thread "main" java.lang.NullPointerException
    ...
           

SomeClass someClass = null;聲明了一個引用someClass并指派為null,someClass.someMethod();嘗試調用null的執行個體方法(java8+也可能通過 someclass::somemethod 文法來調用),會抛出空指針異常;someClass.someField = 10;修改null的屬性,也會抛出空指針異常。

擷取null數組的長度 / 通路或修改null數組中的元素

可以看下面的代碼示例:

int[] nullArray = null;
int length = nullArray.length;
nullArray[0] = 10;
           

輸出:

// 擷取長度空指針
Exception in thread "main" java.lang.NullPointerException
    at ...

           

int[] nullArray = null;聲明了一個引用nullArray并指派為null,int length = nullArray.length;擷取null數組的長度,會抛出空指針異常;nullArray[0] = 10;修改null數組中的元素,也會抛出空指針異常。

正常抛出null異常

出了"被動"觸發空指針異常外,我們還可以手動抛出空指針異常,比如這樣:throw new NullPointerException("null!");。

程式設計中一些空指針異常觸發場景以及規避方法

方法參數中的空指針

方法參數中的空指針觸發場景

當我們對外開放一個方法時,方法的入參就有可能會被調用方傳入null,比如下列方法:

int someMethod(Object someObj){
    someObj.xxx();
    return 0;
}
           

如果調用方傳入null:int res = someMethod(null);,那麼在下面就會導緻空指針異常了。

方法參數中的空指針防範方法

對于這類對外開放的(public/protected)方法來說,保護自己就很重要,防範方法一般是提前檢查入參是否滿足要求,如果不滿足要求則抛出異常,可以通過類庫中的Objects.requireNonNull()來完成,比如:

int someMethod(Object someObj){
    Objects.requireNonNull(someObj, "someObj must not be null");
    someObj.xxx();
}
           

這一條當然不隻适用于我們自己編寫程式的時候,在使用第三方的類庫時,也要注意使用的類庫的方法參數是否支援null,對方也許粗心大意未處理這類異常,自己要多加小心。

方法傳回值中的空指針異常

方法傳回值空指針的觸發場景

使用别人的程式時,除了要注意入參以外,傳回值也要額外留意,确認該方法的傳回值是否會傳回null,防止造成不必要的麻煩。比如:

Integer getArrayLength(int[] num){
    if (num == null){
        return null;
    } else{
        return num.length;
    }
}
           

上述方法會傳回傳入的數組長度,當傳入數組為空時,會傳回null。如果調用時,傳入了一個null數組,并且嘗試用它傳回的值進行加減,那麼就會導緻空指針異常了,如下所示:

int[] array = null;
......很長的代碼之後
Integer length = getArrayLength(array);
// 空指針!
int res = length + 10;
.....
           

這裡的空指針看起來沒有那麼明顯,實際上,在getArrayLength傳回一個裝箱類型Integer的時候,這個異常就埋下了伏筆,在int res = length + 10;時,傳回的length被自動拆箱,導緻了空指針異常。

這也是一種比較罕見的情況,但是出現這類問題可能會有漫長的debug等着我們了。

當傳回值是裝箱類型的時候,務必要特别留意。

除了上面這種以外,還有一類obj.getXX().getXXX()..的調用方法需要額外小心,一旦出現問題很難定位,最好别用。

方法傳回值空指針的防範方法

對于方法的設計者來說,盡量不要傳回null,傳回數組和集合時,盡量傳回一個空數組new SomeObj[0]或者是空集合Collections.emptyXXX(),如果是因為入參存在問題無法正常傳回,及時抛出參數異常,不要默默傳回null;遇到必須傳回null的時候,可以傳回Optional<T>。 盡量不要傳回裝箱類型,除非迫不得已。

對于方法的使用方來說,最好确定方法會不會傳回null并做對應處理。實在無法确定的,最好自己判斷一下空指針。遇到裝箱傳回值的,要警惕自動拆箱。

語句塊中的空指針

這類空指針比較少見,語句塊可以是for/switch/synchronized等。下面依次舉例:

// for語句塊中,iterable為null會導緻空指針
for (element : iterable) 
// switch語句塊中,表達式xxx結果為空會導緻空指針
switch (xxx) { ... }
// synchronized語句塊中,傳入null會導緻空指針異常
synchronized (someNullReference) { ... }
           

防範方法主要是提前檢查。

一些其他的防範方法

string比較時,常量在前面

見代碼:

if ("some string".equals(xxx))
           

使用SonarLint插件(idea)

SonarLint插件不止可以檢查空指針,還可以檢查很多的常見程式設計問題。idea中可以直接在插件市場搜尋安裝。

提示: idea中代碼被标注成背景是黃色時,通常說明可能存在問題,可以把滑鼠挪上去檢視詳情。

jdk14中的空指針異常增強

jdk14中添加了對于空指針異常友好的提示,便于開發者快速定位空指針的對象。示例代碼:

int[] nullArray = null;
nullArray[0] = 10;
           

輸出如下:

Exception in thread "main" java.lang.NullPointerException: Cannot store to int array because "nullArray" is null
           

可以看到比之前的提示友好了很多,可以直接定位問題所在。

繼續閱讀