Java NullPointerException - 检测、修复和最佳实践
java.lang.NullPointerException 是Java 编程中最常见的异常之一 。任何从事 Java 工作的人都一定见过这种异常在 Java 独立程序和 Java Web 应用程序中突然出现。
java.lang.NullPointerException
NullPointerException 是一种运行时异常,因此我们不需要在程序中捕获它。当我们尝试对 null执行某些 需要对象的操作时,应用程序中会引发 NullPointerException。Java 程序中出现 NullPointerException 的一些常见原因如下:
- 调用对象实例上的方法但在运行时该对象为空。
- 访问运行时为空的对象实例的变量。
- 在程序中抛出 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)
我们在语句中得到了 NullPointerException, t.foo("Hi");
因为这里的“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;
由于此处的“t”为空,因此语句中引发了 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. 当为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)
由于“mutex”对象为空,因此将抛出NullPointerException 。
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 500 – Internal 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)
根本原因是语句中的 NullPointerException,user.getUserId().toLowerCase()
因为user.getUserId()
返回的是 null。
如何检测 java.lang.NullPointerException
检测 NullPointerException 非常简单,只需查看异常跟踪,它就会显示异常的类名和行号。然后查看代码,看看什么可能是空的,从而导致 NullPointerException。只需查看上述所有示例,从堆栈跟踪中就可以很清楚地知道是什么导致了空指针异常。
如何修复 NullPointerException
java.lang.NullPointerException 是一个未经检查的异常,所以我们不必捕获它。可以使用空检查和预防性编码技术来防止空指针异常。查看下面的代码示例,了解如何避免 java.lang.NullPointerException
。
if(mutex ==null) mutex =""; //preventive coding
synchronized(mutex) {
System.out.println("synchronized block");
}
//using null checks
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. 我们还可以添加参数的空检查,并 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;
//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文档