我一直想知道为什么 JVM 在抛出 a 时不告诉你哪个指针(或更准确地说,哪个变量)为空NullPointerException
。
行号不够具体,因为有问题的行通常可能包含许多可能导致错误的变量。
是否有任何编译器或 JVM 标志可以使这些异常消息更有用?
我一直想知道为什么 JVM 在抛出 a 时不告诉你哪个指针(或更准确地说,哪个变量)为空NullPointerException
。
行号不够具体,因为有问题的行通常可能包含许多可能导致错误的变量。
是否有任何编译器或 JVM 标志可以使这些异常消息更有用?
这是因为取消引用总是在没有可用名称时发生。该值被加载到操作数堆栈中,然后被传递给取消引用它的 JRE 操作码之一。但是,操作数堆栈没有与空值相关联的名称。它只有“空”。使用一些巧妙的运行时跟踪代码,可以派生名称,但这会增加开销,但价值有限。
因此,没有 JRE 选项可以打开空指针异常的额外信息。
在此示例中,引用存储在本地插槽 1 中,该插槽映射到本地变量名称。但是取消引用发生在 invokevirtual 指令中,它只在堆栈上看到一个“null”值,然后抛出一个异常:
15 aload_1
16 invokevirtual #5
同样有效的是数组加载后跟取消引用,但在这种情况下,没有名称可以映射到“空”值,只是另一个值的索引。
76 aload 5
78 iconst_0
79 aaload
80 invokevirtual #5
您也不能将名称静态分配给每条指令 - 此示例生成大量字节码,但您可以看到取消引用指令将接收 objA 或 objB,您需要动态跟踪以报告正确的,因为两个变量都流向相同的取消引用指令:
(myflag ? objA : objB).toString()
一旦你 JIT 代码,它只是本机指针数学,如果本机代码中的任何指针为空,它就会引发异常。将该程序集反转回原始变量会产生毁灭性的性能影响,并且考虑到 JIT 将生成的代码优化到不同的级别,这通常甚至是不可能的。
从 Java 15 开始,它终于告诉你了!请参阅此 JEP:https ://openjdk.java.net/jeps/358 (它实际上是在 Java 14 中添加的,但默认情况下被禁用,现在在 Java 15 中默认启用)。
如果您将行分成多行而不是在一行上进行多个方法调用,或者如果您在该行上设置断点并使用调试器单步执行该行,您可以很容易地找出哪个引用为空。
如果
行号不够具体,因为有问题的行通常可能包含许多可能导致错误的变量。
那么我建议:
NullPointerException
生成值分配给临时变量。不幸的是,这正是 Java 的工作方式。
如果这是“你的”代码,那么只需添加如下代码片段
if (foo == null) {
throw new NullPointerException("foo == null");
}
在分配 foo 之后。如果 foo 是一个参数,则在方法体的开头立即检查,并抛出 IllegalArgumentException。
这应该可以帮助您澄清问题。
您可以在调试时在Eclipse中对空指针异常添加断点,以获取异常的确切原因。