1. 程式人生 > >Java避免NullPointerException的一些技巧

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

注:轉載自別人的文章,向作者致敬