Java空指针异常完全解决指南:检测、修复与最佳实践详解

这是文章《Java 空指针异常 – 检测、修复和最佳实践》的第1部分(共8部分)。

java.lang.NullPointerException是Java编程中最常见的异常之一。在Java的独立程序和Web应用程序中,任何使用Java的人都可能突然遭遇到这个异常。

java.lang.NullPointerException 概述

java lang nullpointerexception hierarchy

空指针异常(NullPointerException)是一种运行时异常,因此我们在程序中不需要捕获它。当应用程序试图对null值执行某些需要对象的操作时,就会引发空指针异常。Java程序中空指针异常的一些常见原因是:

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

Java 空指针异常示例

让我们来看一些在Java程序中出现空指针异常的例子。

1. 调用实例方法时发生空指针异常

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());
    }
}

当我们运行上述程序时,它抛出以下空指针异常错误消息:

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

我们在”t.foo(“Hi”)”语句中遇到NullPointerException,因为在这里”t”是空的。

2. 当访问/修改空对象的字段时发生Java的空指针异常

这是文章《Java 空指针异常 – 检测、修复和最佳实践》的第2部分(共8部分)。

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;
	}

}

上述程序抛出了以下空指针异常的堆栈跟踪。

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

由于变量”t”为空,语句”int i = t.x;”抛出了空指针异常(NullPointerException)。

当在方法参数中传递空值时,Java会出现空指针异常

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中执行上述程序时出现的空指针异常。

Exception in thread main java.lang.NullPointerException

当抛出 null 时,会出现 java.lang.NullPointerException

抛出null值导致的空指针异常

public class Temp {

	public static void main(String[] args) {

		throw null;
	}

}

以下是上述程序的异常堆栈跟踪,显示了由于抛出”null”值而引发的空指针异常(NullPointerException)。

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

获取空数组长度时的空指针异常

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)

访问空数组索引值时的空指针异常

当尝试访问空数组的索引值时,同样会发生空指针异常。这是因为空数组没有分配内存空间,无法访问其任何元素。

访问空数组元素导致的空指针异常

public class Temp {

	public static void main(String[] args) {

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

}
异常 在线程 "main" java.lang.NullPointerExceptionTemp.main(Temp.java:7)

在空对象上进行同步导致的空指针异常

public class Temp {

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

		synchronized(mutex) {
			System.out.println("同步代码块");
		}		
	}
}

由于mutex对象为空,同步操作synchronized(mutex)将引发空指针异常(NullPointerException)。

HTTP状态500与空指针异常的关系

HTTP状态码为500,表示服务器内部出现了空指针异常。当Java Web应用程序中发生未捕获的空指针异常时,服务器通常会返回HTTP 500错误,表明这是一个服务器内部错误。

有时候我们从Java网络应用程序得到一个错误页面响应,错误信息显示为”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("用户页面请求");
		System.out.println("用户名: "+user.getUserName().toLowerCase());
		System.out.println("用户ID: "+user.getUserId().toLowerCase());
		model.addAttribute("userName", user.getUserName());
		return "user";
	}

下面的图片显示了网页应用程序响应时所抛出的错误信息。

HTTP 500错误 - Java空指针异常

这是异常的堆栈跟踪:

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.scdev.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中的NullPointerException

检测到空指针异常非常简单,只需查看异常跟踪,它会显示出异常的类名和行号。然后查看代码,看看可能是什么造成了空指针异常。只需查看以上所有示例,从堆栈跟踪中就很清楚是什么引起了空指针异常。

如何修复空指针异常

java.lang.NullPointerException是一个未经检查的异常,所以我们不需要捕获它。可以通过使用空检查和预防性编码技术来避免空指针异常。请看下面的代码示例,展示了如何避免java.lang.NullPointerException。

if(mutex ==null) mutex =""; //预防性编码
		
synchronized(mutex) {
	System.out.println("同步块");
}
//使用空检查
if(user!=null && user.getUserName() !=null) {
System.out.println("用户名: "+user.getUserName().toLowerCase());
}
if(user!=null && user.getUserName() !=null) {
	System.out.println("用户ID: "+user.getUserId().toLowerCase());
}

避免空指针异常的编码最佳实践

让我们考虑以下函数,并寻找可能引发空指针异常的情况。

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

如果将参数传递为null,可能会发生空指针异常。为了避免空指针异常,可以将同样的方法写成以下形式。

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

我们还可以对参数进行空值检查,如果需要的话,抛出非法参数异常。

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

我们可以像下面的例子代码一样使用三元运算符。

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

4. 使用String.valueOf()方法而不是toString()方法。例如,检查打印流的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());

尽可能编写返回空对象的方法,例如空列表、空字符串等,而不是返回null。

在集合类中定义了一些方法来避免空指针异常,你应该使用它们。例如contains()方法、containsKey()方法和containsValue()方法。

参考:API文档

bannerAds