Java避免NullPointerException的一些技巧
那些情況會引發該異常呢?
-
被呼叫方法的物件為null。
-
訪問或修改一個null物件的欄位。
-
求一個數組為null物件的長度。
-
訪問或修改一個數組為null物件中的某一個值。
-
被丟擲的值是null並且是一個Throwable的子類。
-
當你用null物件進行synchronized程式碼塊。
NullPointerException 是 RuntimeException 的子類,因此,Javac 編譯器並不會強迫你使用 try-catch 程式碼塊來捕獲該異常。
一、為什麼需要 null ?
如上所述,null 是 Java 的一個特殊值。它在設計模式方面編碼的過程中非常有用,例如空物件模式和單例模式。空物件模式提供了一個物件作為缺少給定型別物件的代理。而單例模式可以確保只建立一個類的例項,主要用於提供一個全域性訪問的物件。
例如,建立單例類的示例方法是將其所有建構函式宣告為private,然後建立一個返回該類的唯一例項的公共方法,如下:
import java.util.UUID; public class TestSingleton { public static void main(String[] args) { Singleton s=Singleton.getInstance(); System.out.println(s.getID()); } } class Singleton{ private static Singleton singleton = null; private String Id = null; private Singleton() { Id=UUID.randomUUID().toString(); } public static Singleton getInstance() { if(singleton == null) { singleton = new Singleton(); } return singleton; } public String getID() { return this.Id; } }
在這個例子中,我們聲明瞭一個 Singleton 類的靜態例項。該例項在 getInstance 方法內最多初始化一次。注意本例使用了 null 來確保建立的是唯一例項。
二、如何避免空指標異常
為了避免這種情況 NullPointerException ,請確保在使用執行程式之前,所有物件都已正確初始化。注意,當你宣告一個引用變數時,即建立了一個指向物件的指標。在向物件請求方法或欄位之前,您必須驗證變數「指標」是否為空。
另外,如果引發異常,請使用堆疊中的異常資訊進行跟蹤。堆疊跟蹤是由JVM提供,便於應用程式的除錯。找到發生異常的方法和程式碼行,然後確定哪個引用為null。
在本文的餘下部分,我們將介紹一些避免空指標異常的方法。但是,這些方法並非一勞永逸,因此,同學們在編寫應用程式時應格外小心。
1、String變數與文字值比較
在編碼過程中,String變數與文字值之間的比較是特別常見的。一般被比較的值可以是一個字串或列舉值。因此,我們不要從空物件呼叫方法進行比較,而應考慮從文字值中呼叫方法。如下:
上面的程式碼片段則會丟擲一個NullPointerException。但是,如果我們從文字中呼叫方法,那麼執行流程通常會繼續:
為什麼呢?去看一下 JDK 原始碼 java.lang.String 便明白了。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
2、檢查方法的引數
在執行你自己的方法的主體之前,一定要檢查方法傳入的引數是否為空。只有在正確檢查了引數後,才能繼續執行該方法的相應邏輯。否則,您可以丟擲一個 IllegalArgumentException 來通知呼叫方法所傳遞的引數有問題。
例如:
3、優先使用String.valueOf() 代替toString()
當您程式碼中的某個物件需要用字串的方式來表示時,請避免使用該物件的toString方法;因為若你的物件引用為null,則會丟擲 NullPointerException。
相反,考慮使用靜態String.valueOf方法,該方法不會丟擲任何異常,若物件引用為空,則列印「null」字串。
有的同學可能會問為什麼呢?還是那句話讀原始碼 ^_^
下面是基類 Object 的 toString() 方法:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
接著,咱們再來看看 String 的 valueOf() 方法做了什麼呢?
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
4、使用三元運算子
該操作是非常有用的,可以幫助我們避免了NullPointerException。格式如下
boolean expersion ? value1 : value2
上面通過布林表示式來判斷。如果表示式結果為true,則返回value1,否則返回value2。我們可以使用三元運算子來處理空指標,如下所示:
String message = ( str == null) ? " " : str.substring( 0,10 );
如果str的引用為空,則訊息變數將為空。否則,如果str指向實際資料,則該訊息將保留它的前10個字元。
這裡,一直有個疑惑困擾著我,為什麼 Kotlin 這門語言去掉了三元運算子呢?知道的同學歡迎留言,一起來探討~~~
5、建立返回空集合而不是null值的方法
一個非常好的操作是建立返回一個空集合的方法,而不是一個null值。因為你的程式碼可以遍歷空集合並使用它的方法和欄位,而不會丟擲一個NullPointerException 。例如:
public class Example {
public static List<Integer> number = null;
public static List<Integer> getList(){
if(number == null)
return Collections.emptyList();
else
return number;
}
}
注意:要熟悉 Collections 這個集合工具類,裡面有太多好用的方法了。
6、使用Apache的StringUtils類
Apache的Commons Lang是一個為 java.lang API 提供幫助工具的庫,比如字串操作方法。提供字串操作的示例類是 StringUtils.java,它對輸入的字串進行了 null 判斷。
你可以使用 StringUtils.isNotEmpty, StringUtils.IsEmpty 和 StringUtils.equals 等方法,來避免NullPointerException。例如:
if(StringUtils.isNotEmpty(str)){
System.out.println(str.toString)
}
7、習慣用 contains(), containsKey(), containsValue() 方法
如果您的程式在使用集合,請考慮使用contains,containsKey和containsValue方法。例如,從集合中找一個特定鍵的值:
Map <String,String> map=
....
String key = ...
String value = map.get(key);
System.out.println (value.toString());
在上面的程式碼片段中,我們未檢查key是否真的存在於內部Map,因此返回的值可以是 null 。最安全的方法如下:
Map <String,String> map=
....
String key = ...
if(map.containsKey(key)){
String value = map.get(key);
System.out.println (value.toString());
}
8、請檢查使用的外部方法的返回值是否為 null
在編碼中使用外部庫是很常見的,這些庫可能包含返回引用的方法,需確保返回的值不為 null 。另外,我們要養成在開發的過程中,養成閱讀 Javadoc 的習慣,以便更好地理解其功能和返回值。
9、使用斷言
斷言在測試程式碼時非常有用,並且可以被使用,以避免 NullPointerException 。Java 斷言是用 assert 關鍵字實現的,並丟擲一個 AssertionError 。
請注意,您必須顯式啟用 JVM 的斷言標誌,一般在程式啟動時,使用 –ea 引數來啟用斷言。否則,斷言將被完全忽略。
使用 Java 斷言的示例如下:
public static int getLengths(String s) {
assert(s != null);
return s.length();
}
如果您執行上面的程式碼段並傳遞一個空引數getLength,則會出現以下錯誤訊息:
Exception in thread "main" java.lang.NullPointerException
最後,您可以使用測試框架 JUnit 提供的類 Assert 來使用斷言。
10、單元測試
在測試程式碼的功能和正確性時,單元測試一般非常有用。因此,建議多花一些時間編寫一些測試用例,來避免程式出現 NullPointerException。目前,我司的程式碼覆蓋率要達到 95% 以上才能通過。
三、擁有 NullPointerException 的安全方法
1、訪問類的靜態成員或方法
當你的程式碼試圖訪問靜態變數或類的方法時,即使物件的引用等於 null,JVM 也不會丟擲一個 NullPointerException 。這是由於Java編譯器在編譯過程中將靜態方法和欄位儲存在方法區或者常量池。因此,靜態欄位和方法不與物件相關聯,而與類的名稱相關聯。
例如,下面的程式碼不會丟擲NullPointerException:
public class Test {
public static void main(String[] args) {
SimpleClass s = null;
s.printMessage();
}
}
class SimpleClass{
public static void printMessage() {
System.out.println("Hello My New World");
}
}
注意,儘管 SampleClass 等於的例項 null 將會被正確執行。但是,對於靜態方法或欄位,最好以靜態方式訪問它們,比如SampleClass.printMessage()。
2、instanceof 操作符
instanceof 即使物件的引用等於 null,也可以使用該運算子。在 instanceof 操作時,參考值等於為null,不丟擲 NullPointerException,而是返回 false 。例如,下面的程式碼片段:
String str = null;
if( str instanceof String)
System.out.println("It's an instance of the String class");
else
System.out.println("Not an instance of the String class");
正如預期的那樣,執行的結果是:
Not an instance of the String class
注:轉載自別人的文章,向作者致敬