引入
NullPointerException
是Java开发中经常会遇到的异常。在JDK 14之前的版本中,NullPointerException
异常的消息只是简单的null
,并不会告诉你任何有用的信息,只能根据异常产生的源文件的行号来查找。对于很长的引用链来说,很难定位到底是哪个对象为null
。比如,类似a.b.c.d
这样的引用方式,a
、b
和c
中的任何一个为null
,都会出现NullPointerException
异常。仅靠行号无法快速定位问题所在。
在下面的代码中,对p.address.go();
的调用会出现NullPointerException
异常。
public class Npe {
public static void main(String[] args) {
People p = new People();
p.address.go();
}
}
class People{
Address address;
}
class Address{
public void go(){
System.out.println("我们出去旅游吧");
}
}
直接运行该文件的错误信息如下所示,从中我们只可以知道错误出现在6行,但是并不清楚错误的具体对象是谁为null
Exception in thread "main" java.lang.NullPointerException
at com.itheima.hello.Npe.main(Npe.java:6)
详解
JEP 358增强了对NullPointerException
异常的处理,可以显示详细的信息。这个功能需要通过选项-XX:+ShowCodeDetailsInExceptionMessages
启用,如下所示:
java -XX:+ShowCodeDetailsInExceptionMessages Npe
输出的信息如下所示。错误消息明确的指出了a
为null
。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Address.go()" because "<local1>.address" is null
at NpeDemo01.main(NpeDemo01.java:4)
其它情况示范
但是对于更复杂的代码,不使用调试器就无法确定哪个变量为空。假设下面的代码中出现了一个NPE:
a.b.c.i = 99;
仅仅使用文件名和行数,并不能精确定位到哪个变量为null,是a、b还是c?
访问数组也会发生类似的问题。假设此代码中出现一个NPE:
a[i][j][k] = 99;
文件名和行号不能精确指出哪个数组组件为空。是a还是a[i]
或a[i][j]
?
一行代码可能包含多个访问路径,每个访问路径都可能是NPE的来源。假设此代码中出现一个NPE:
a.i = b.j;
文件名和行号并不能确定哪个对象为空,是a还是b?
NPE也可能在方法调用中传递,看下面的代码:
x().y().i = 99;
文件名和行号不能指出哪个方法调用返回null。是x()还是y()?
详细示例
接下来我们来示范一下如下的情况下:a.i = b.j
public class NpeDemo01 {
public static void main(String[] args) {
A a = new A();
B b =null;
System.out.println(a.i == b.j);
}
}
class A{
int i;
}
class B{
int j;
}
直接执行代码
java -XX:+ShowCodeDetailsInExceptionMessages NpeDemo01.java
异常信息
Exception in thread "main" java.lang.NullPointerException: Cannot read field "j" because "<local2>" is null at NpeDemo01.main(NpeDemo01.java:5)