Java空指針異常 – 檢測、修復和最佳實踐

java.lang.NullPointerException是Java编程中最常见的异常之一。在Java独立程序和Java Web应用程序中,任何从无处冒出的异常都可能是java.lang.NullPointerException。

java.lang.NullPointerException

NullPointerException是运行时异常,因此我们不需要在程序中捕获它。当我们在应用程序中尝试对null进行一些操作时,会引发NullPointerException,而此时需要一个对象。Java程序中NullPointerException的一些常见原因是:

  1. 在对象实例上调用方法,但在运行时对象为null。
  2. 访问在运行时为null的对象实例的变量。
  3. 在程序中抛出null。
  4. 访问或修改为null的数组的索引或索引值。
  5. 检查运行时为null的数组的长度。

Java NullPointerException示例

讓我們來看一些在Java程式中的NullPointerException的例子。

1. 呼叫實例方法時發生NullPointerException

public class Temp {

	public static void main(String[] args) {

		Temp t = initT();
		
		t.foo("Hi");
		
	}

	private static Temp initT() {
		return null;
	}

	public void foo(String s) {
		System.out.println(s.toLowerCase());
	}
}

執行上述程式時,會拋出以下NullPointerException錯誤訊息。

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

在這個陳述式t.foo("Hi");中,我們得到NullPointerException,因為這裡的”t”是null。

2. 存取/修改空物件的欄位時發生Java NullPointerException

public class Temp {

	public int x = 10;
	
	public static void main(String[] args) {

		Temp t = initT();
		
		int i = t.x;
		
	}

	private static Temp initT() {
		return null;
	}

}

上述程式會產生以下NullPointerException堆疊追蹤。

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:9)

在這個陳述式int i = t.x;中,因為這裡的”t”是null,所以會拋出NullPointerException。

3. 傳遞null作為方法引數時發生Java NullPointerException

public class Temp {

	public static void main(String[] args) {

		foo(null);

	}

	public static void foo(String s) {
		System.out.println(s.toLowerCase());
	}
}

這是java.lang.NullPointerException的最常見情況之一,因為是調用者傳遞了空參數。

下面的圖像顯示了在Eclipse IDE中執行上述程序時的空指針異常。

4. 當空值被拋出時發生java.lang.NullPointerException

public class Temp {

	public static void main(String[] args) {

		throw null;
	}

}

以下是上述程序的異常堆棧跟踪,顯示由於throw null;語句而引起的NullPointerException。

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:5)

5. 當為空數組獲取長度時發生NullPointerException

public class Temp {

	public static void main(String[] args) {

		int[] data = null;
		
		int len = data.length;
	}

}
Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

6. 當訪問空數組的索引值時發生NullPointerException

public class Temp {

	public static void main(String[] args) {

		int[] data = null;
		
		int len = data[2];
	}

}
Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

7. 當對空對象進行同步時出現NullPointerException

public class Temp {

	public static String mutex = null;
	
	public static void main(String[] args) {

		synchronized(mutex) {
			System.out.println("synchronized block");
		}		
	}
}

synchronized(mutex)會拋出NullPointerException,因為“mutex”對象為空。

8. HTTP 狀態 500 java.lang.NullPointerException

有時我們從一個Java Web應用程序得到一個錯誤頁面響應,錯誤消息為“HTTP 狀態 500 – 內部服務器錯誤”,根本原因為java.lang.NullPointerException

為此,我編輯了Spring MVC 示例項目並更改了HomeController方法如下。

@RequestMapping(value = "/user", method = RequestMethod.POST)
	public String user(@Validated User user, Model model) {
		System.out.println("User Page Requested");
		System.out.println("User Name: "+user.getUserName().toLowerCase());
		System.out.println("User ID: "+user.getUserId().toLowerCase());
		model.addAttribute("userName", user.getUserName());
		return "user";
	}

下面的圖像顯示了Web應用程序響應拋出的錯誤消息。

以下是異常堆棧跟踪:

HTTP Status 500Internal Server Error

Type Exception Report

Message Request processing failed; nested exception is java.lang.NullPointerException

Description The server encountered an unexpected condition that prevented it from fulfilling the request.

Exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
	org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Root Cause

java.lang.NullPointerException
	com.journaldev.spring.controller.HomeController.user(HomeController.java:38)
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	java.lang.reflect.Method.invoke(Method.java:498)

根本原因是在語句user.getUserId().toLowerCase()中出現NullPointerException,因為user.getUserId()返回為空。

如何檢測 java.lang.NullPointerException

檢測 NullPointerException 非常簡單,只需查看異常跟踪,它將顯示異常的類名和行號。然後查看代碼,看看可能是什麼為空導致了 NullPointerException。只需查看上述所有示例,從堆棧跟踪中非常清楚地知道是什麼導致了空指針異常。

如何修復 NullPointerException

java.lang.NullPointerException 是一個未檢查的異常,因此我們不必捕獲它。可以通過空檢查和預防性編碼技術來預防空指針異常。請查看下面的代碼示例,顯示如何避免 java.lang.NullPointerException。

if(mutex ==null) mutex =""; //預防性編碼
		
synchronized(mutex) {
	System.out.println("synchronized block");
}
//使用空檢查
if(user!=null && user.getUserName() !=null) {
System.out.println("User Name: "+user.getUserName().toLowerCase());
}
if(user!=null && user.getUserName() !=null) {
	System.out.println("User ID: "+user.getUserId().toLowerCase());
}

避免 NullPointerException 的編碼最佳實踐

1. 讓我們考慮以下函數並注意可能導致空指針異常的情況。

public void foo(String s) {
    if(s.equals("Test")) {
	System.out.println("test");
    }
}

如果傳遞的參數為 null,則可能發生 NullPointerException。為了避免 NullPointerException,可以將同一方法寫成以下形式。

public void foo(String s) {
	if ("Test".equals(s)) {
		System.out.println("test");
	}
}

2. 我們也可以為參數添加 null 檢查,並在必要時拋出 IllegalArgumentException。

public int getArrayLength(Object[] array) {
	
	if(array == null) throw new IllegalArgumentException("array is null");
	
	return array.length;
}

3. 我們可以使用三元運算符,如下面的示例代碼所示。

String msg = (str == null) ? "" : str.substring(0, str.length()-1);

4. 使用 String.valueOf() 而不是 toString() 方法。例如,檢查 PrintStream println() 方法代碼的定義如下。

public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
        }
    }

下面的代碼片段顯示了在 toString() 方法之外使用 valueOf() 方法的示例。

Object mutex = null;

//prints null
System.out.println(String.valueOf(mutex));

//will throw java.lang.NullPointerException
System.out.println(mutex.toString());

5. 在可能的情況下,寫方法返回空對象,而不是 null,例如,空列表,空字符串等。

6. 在集合類中定義了一些方法來避免 NullPointerException,您應該使用它們。例如 contains()、containsKey() 和 containsValue()。

參考:API 文檔

Source:
https://www.digitalocean.com/community/tutorials/java-lang-nullpointerexception