什么是NullPointerException
Java 中有两种主要类型的变量:
- 原始类型:包含数据的变量。如果要操作原始变量中的数据,可以直接操作该变量。按照惯例,原始类型以小写字母开头。例如,
int
或类型的变量char
是原始的。 - 引用:包含内存地址的变量,
Object
即引用的变量Object
。如果要操作Object
引用变量引用的,则必须取消引用它。取消引用通常需要使用.
来访问方法或字段,或使用[
来索引数组。按照惯例,引用类型通常用大写字母开头的类型表示。例如,类型的变量Object
是引用。
考虑以下代码,其中声明一个原始类型的变量int
但不初始化它:
int x;
int y = x + x;
这两行将导致程序崩溃,因为没有指定 的值,x
而我们正尝试使用x
的值来指定y
。所有原语在被操作之前都必须初始化为可用值。
现在事情变得有趣了。引用变量可以设置为,null
这意味着“我没有引用任何东西”。如果您明确地这样设置引用变量,则可以获取null
引用变量中的值,或者引用变量未初始化,编译器无法捕获它(Java 会自动将变量设置为null
)。
如果引用变量由您明确设置或通过 Java 自动设置为 null,并且您尝试取消引用它,则会得到NullPointerException
。
( NPE NullPointerException
) 通常发生在您声明变量但在尝试使用变量的内容之前未创建对象并将其分配给变量时。因此,您引用了实际上不存在的内容。
以下面的代码为例:
Integer num;
num = new Integer(10);
第一行声明了一个名为 的变量num
,但它实际上还没有包含引用值。由于您尚未指定指向什么,因此 Java 将其设置为null
。
第二行,new
使用关键字实例化(或创建)类型为的对象Integer
,并将引用变量num
分配给该Integer
对象。
如果您尝试在创建对象num
之前NullPointerException
取消引用,则会得到。在大多数普通情况下,编译器都会捕获问题并让您知道“ num may not have been initialized
”,但有时您可能会编写不直接创建对象的代码。
例如,您可能有如下方法:
public void doSomething(SomeObject obj) {
// Do something to obj, assumes obj is not null
obj.myMethod();
}
在这种情况下,您不是在创建对象obj
,而是假设它是在doSomething()
调用该方法之前创建的。请注意,可以像这样调用该方法:
doSomething(null);
在这种情况下,obj
是null
,并且语句obj.myMethod()
将抛出NullPointerException
。
如果该方法旨在对传入的对象执行某些操作(如上述方法所示),则抛出是适当的,NullPointerException
因为这是一个程序员错误,程序员需要该信息来进行调试。
除了NullPointerException
由于方法的逻辑而抛出的 s 之外,您还可以检查方法参数的null
值,并通过在方法开头附近添加如下内容来明确抛出 NPE:
// Throws an NPE with a custom error message if obj is null
Objects.requireNonNull(obj, "obj must not be null");
请注意,在错误消息中清楚地说明哪个对象不能是会很有帮助null
。验证这一点的好处是 1) 您可以返回自己更清晰的错误消息,2) 对于该方法的其余部分,您知道除非obj
重新分配,否则它不为空并且可以安全地取消引用。
或者,在某些情况下,该方法的目的不仅仅是对传入的对象进行操作,因此空参数可能是可以接受的。在这种情况下,您需要检查空参数并采取不同的行为。您还应该在文档中解释这一点。例如,doSomething()
可以写成:
/**
* @param obj An optional foo for ____. May be null, in which case
* the result will be ____.
*/
public void doSomething(SomeObject obj) {
if(obj == null) {
// Do something
} else {
// Do something else
}
}
可以使用哪些方法/工具来确定原因,以便阻止异常导致程序过早终止?
Sonar 配合 find bugs 可以检测 NPE。Sonar 能否动态捕获 JVM 导致的空指针异常
现在 Java 14 添加了一项新语言功能来显示 NullPointerException 的根本原因。此语言功能自 2006 年以来一直是 SAP 商业 JVM 的一部分。
在 Java 14 中,以下是 NullPointerException 异常消息的示例:
在线程“main”中 java.lang.NullPointerException: 无法调用“java.util.List.size()”,因为“list”为空
NullPointerException导致发生的情况列表
NullPointerException
以下是Java 语言规范中直接*提到的所有出现的情况:
- 访问(即获取或设置)空引用的实例字段。(静态字段不算!)
- 调用空引用的实例方法。(静态方法不算!)
throw null;
- 访问空数组的元素。
- 同步为空 –
synchronized (someNullReference) { ... }
- 如果任何整数/浮点运算符
NullPointerException
的一个操作数是装箱的空引用,则该运算符可能会抛出 NullPointerException
如果装箱值为空,则取消装箱转换会抛出一个异常。- 调用
super
空引用会抛出一个NullPointerException
。如果你感到困惑,这是在谈论合格的超类构造函数调用:
class Outer {
class Inner {}
}
class ChildOfInner extends Outer.Inner {
ChildOfInner(Outer o) {
o.super(); // if o is null, NPE gets thrown
}
}
- 使用
for (element : iterable)
循环循环遍历空集合/数组。 switch (foo) { ... }
(无论是表达式还是语句)NullPointerException
当foo
为空时都会抛出。foo.new SomeInnerClass()
NullPointerException
当为空时抛出foo
。- 形式为
name1::name2
或 的方法引用在评估时primaryExpression::name
抛出,当或评估为空时。NullPointerException
name1
primaryExpression
此处 JLS 的一条注释指出,someInstance.someStaticMethod()
不会抛出 NPE,因为someStaticMethod
是静态的,但someInstance::someStaticMethod
仍然会抛出 NPE!