Java 空指针异常 – 检测、修复和最佳实践

java.lang.NullPointerException 是 Java 编程中最常见的异常之一。任何在 Java 中工作的人都可能在 Java 独立程序以及 Java web 应用程序中突然遇到这个异常。

java.lang.NullPointerException

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

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

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”为空。

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; 中抛出 NullPointerException,因为此处的“t”为空。

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. 当抛出null时发生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()返回null。

如何检测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();
        }
    }

下面的代码片段显示了使用valueOf()方法而不是toString()的示例。

Object mutex = null;

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

//将抛出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