什麼是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
可以看到比之前的提示友好了很多,可以直接定位問題所在。