什么是JAVA中的空指針(NullPointerExceptions),如何避免程序中的空指針?
空指針異??梢哉f是java中最常見的異常之一了,你對它又了解多少呢?
什么是空指針
眾所周知,JAVA中有兩大類類型:基本類型和引用類型?;绢愋涂梢杂删幾g器保證使用前必須初始化為一個有效值(但是不一定是業(yè)務(wù)的有效值),使用它不會導(dǎo)致空指針異常;而引用類型可以被初始化為一個特殊的值null,說明該引用未引用任何值。
當(dāng)使用一個null對象的屬性,或調(diào)用其方法時,會觸發(fā)空指針異常(NullPointerExceptions)。下面是官方文檔給出的拋出異常的情況:
- Calling the instance method of a null object.(調(diào)用
null的實例方法) - Accessing or modifying the field of a null object.(調(diào)用或者修改
null的屬性) - Taking the length of null as if it were an array.(獲取
null數(shù)組的長度) - Accessing or modifying the slots of null as if it were an array.(訪問或修改
null數(shù)組中的元素) - Throwing null as if it were a Throwable value.(常規(guī)拋出null異常)
調(diào)用null的實例方法 / 調(diào)用或者修改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();嘗試調(diào)用null的實例方法(java8+也可能通過 someclass::somemethod 語法來調(diào)用),會拋出空指針異常;someClass.someField = 10;修改null的屬性,也會拋出空指針異常。
獲取null數(shù)組的長度 / 訪問或修改null數(shù)組中的元素
可以看下面的代碼示例:
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數(shù)組的長度,會拋出空指針異常;nullArray[0] = 10;修改null數(shù)組中的元素,也會拋出空指針異常。
常規(guī)拋出null異常
出了"被動"觸發(fā)空指針異常外,我們還可以手動拋出空指針異常,比如這樣:throw new NullPointerException("null!");。
編程中一些空指針異常觸發(fā)場景以及規(guī)避方法
方法參數(shù)中的空指針
方法參數(shù)中的空指針觸發(fā)場景
當(dāng)我們對外開放一個方法時,方法的入?yún)⒕陀锌赡軙徽{(diào)用方傳入null,比如下列方法:
int someMethod(Object someObj){
someObj.xxx();
return 0;
}
如果調(diào)用方傳入null:int res = someMethod(null);,那么在下面就會導(dǎo)致空指針異常了。
方法參數(shù)中的空指針防范方法
對于這類對外開放的(public/protected)方法來說,保護(hù)自己就很重要,防范方法一般是提前檢查入?yún)⑹欠駶M足要求,如果不滿足要求則拋出異常,可以通過類庫中的Objects.requireNonNull()來完成,比如:
int someMethod(Object someObj){
Objects.requireNonNull(someObj, "someObj must not be null");
someObj.xxx();
}
這一條當(dāng)然不只適用于我們自己編寫程序的時候,在使用第三方的類庫時,也要注意使用的類庫的方法參數(shù)是否支持null,對方也許粗心大意未處理這類異常,自己要多加小心。
方法返回值中的空指針異常
方法返回值空指針的觸發(fā)場景
使用別人的程序時,除了要注意入?yún)⒁酝?,返回值也要額外留意,確認(rèn)該方法的返回值是否會返回null,防止造成不必要的麻煩。比如:
Integer getArrayLength(int[] num){
if (num == null){
return null;
} else{
return num.length;
}
}
上述方法會返回傳入的數(shù)組長度,當(dāng)傳入數(shù)組為空時,會返回null。如果調(diào)用時,傳入了一個null數(shù)組,并且嘗試用它返回的值進(jìn)行加減,那么就會導(dǎo)致空指針異常了,如下所示:
int[] array = null;
......很長的代碼之后
Integer length = getArrayLength(array);
// 空指針!
int res = length + 10;
.....
這里的空指針看起來沒有那么明顯,實際上,在getArrayLength返回一個裝箱類型Integer的時候,這個異常就埋下了伏筆,在int res = length + 10;時,返回的length被自動拆箱,導(dǎo)致了空指針異常。
這也是一種比較罕見的情況,但是出現(xiàn)這類問題可能會有漫長的debug等著我們了。
當(dāng)返回值是裝箱類型的時候,務(wù)必要特別留意。
除了上面這種以外,還有一類obj.getXX().getXXX()..的調(diào)用方法需要額外小心,一旦出現(xiàn)問題很難定位,最好別用。
方法返回值空指針的防范方法
對于方法的設(shè)計者來說,盡量不要返回null,返回數(shù)組和集合時,盡量返回一個空數(shù)組new SomeObj[0]或者是空集合Collections.emptyXXX(),如果是因為入?yún)⒋嬖趩栴}無法正常返回,及時拋出參數(shù)異常,不要默默返回null;遇到必須返回null的時候,可以返回Optional<T>。 盡量不要返回裝箱類型,除非迫不得已。
對于方法的使用方來說,最好確定方法會不會返回null并做對應(yīng)處理。實在無法確定的,最好自己判斷一下空指針。遇到裝箱返回值的,要警惕自動拆箱。
語句塊中的空指針
這類空指針比較少見,語句塊可以是for/switch/synchronized等。下面依次舉例:
// for語句塊中,iterable為null會導(dǎo)致空指針
for (element : iterable)
// switch語句塊中,表達(dá)式xxx結(jié)果為空會導(dǎo)致空指針
switch (xxx) { ... }
// synchronized語句塊中,傳入null會導(dǎo)致空指針異常
synchronized (someNullReference) { ... }
防范方法主要是提前檢查。
一些其他的防范方法
string比較時,常量在前面
見代碼:
if ("some string".equals(xxx))
使用SonarLint插件(idea)
SonarLint插件不止可以檢查空指針,還可以檢查很多的常見編程問題。idea中可以直接在插件市場搜索安裝。
提示: idea中代碼被標(biāo)注成背景是黃色時,通常說明可能存在問題,可以把鼠標(biāo)挪上去查看詳情。
jdk14中的空指針異常增強(qiáng)
jdk14中添加了對于空指針異常友好的提示,便于開發(fā)者快速定位空指針的對象。示例代碼:
int[] nullArray = null;
nullArray[0] = 10;
輸出如下:
Exception in thread "main" java.lang.NullPointerException: Cannot store to int array because "nullArray" is null
可以看到比之前的提示友好了很多,可以直接定位問題所在。