【问题标题】:Why doesn't Java tell you which pointer is null?为什么 Java 不告诉你哪个指针是空的?
【发布时间】:2009-07-22 21:05:39
【问题描述】:

我一直想知道为什么当抛出NullPointerException 时JVM 不告诉你哪个 指针(或更准确地说,哪个变量)为空。

行号不够具体,因为违规行通常包含许多可能导致错误的变量。

是否有任何编译器或 JVM 标志可以使这些异常消息更有用?

【问题讨论】:

  • 在我作为一名专业的 Java 开发人员 8 年多的时间里,这对我来说真的从来没有成为问题。如果您在一行中有很多可能为空的引用,则可能是时候将其分成多行了。
  • @mattc,您从不使用其他人的库代码?
  • @MattC:当您需要在长 if-else-if 块中间的条件中调用具有多个参数的函数时,通常会出现这种情况。将它分成多行意味着在if 的顶部之前向上声明一堆虚拟变量。我会说,在大多数情况下,这在代码可读性和样式方面会更有问题。
  • 我就同一问题提出了类似的如何调试问题,但最终被说服以一种不会引发 NPE 的方式编写我的代码。见*.com/questions/410890/…

标签: java debugging nullpointerexception


【解决方案1】:

这是因为取消引用总是在没有可用名称时发生。该值被加载到操作数堆栈中,然后被传递给取消引用它的 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()

【讨论】:

  • +1 对于实际的解释,而不是“如果你以'正确的方式'编写代码,这不会发生在你身上”。
  • JVM 应该能够告诉我们是哪条指令导致了 NPE,我认为这仍然非常有用,例如,如果表达式“a.b.c.d.e.f”导致 NPE。
【解决方案2】:

一旦你 JIT 代码,它只是原生指针数学,如果原生代码中的任何指针为空,它就会抛出异常。将该程序集反转回原始变量会产生毁灭性的性能影响,并且考虑到 JIT 将生成的代码优化到不同的级别,这通常甚至是不可能的。

【讨论】:

  • @Michael:JIT 在本机代码生成期间跟踪行号。它不会以更高的分辨率进行跟踪(子表达式等)。
  • @Michael:对不起,我是从 CLI JIT 发言的:请参阅此处“默认”成员的描述:msdn.microsoft.com/en-us/library/…
  • @Michael,早期的 Sun JIT 丢失了行号信息。 Hotspot 甚至可以针对 jitted 代码显示。
  • 这个迷你线程为我澄清了它,所以我添加了我的答案。无论如何,在使用 ?: 运算符时,跟踪子表达式并不能解决问题。
  • jitting 代码后,还有很多信息,比如行号。我认为这种解释到一半就停止了。
【解决方案3】:

从 Java 15 开始,它终于告诉你了!请参阅此 JEP:https://openjdk.java.net/jeps/358(它实际上是在 Java 14 中添加但默认禁用,现在在 Java 15 中默认启用)。

【讨论】:

    【解决方案4】:

    如果您将行分成多行而不是在一行上进行多个方法调用,或者如果您在该行上设置断点并使用调试器单步执行该行,您可以很容易地找出哪个引用为空。

    【讨论】:

      【解决方案5】:

      如果

      行号不够具体 因为有问题的线路经常可以 包含许多变量,可以 导致错误。

      那我建议:

      1. 将该行分成多行并将可能的NullPointerException 生成值分配给临时变量。
      2. 使用调试器并单步执行每个方法调用,直到找到导致问题的方法。

      【讨论】:

        【解决方案6】:

        不幸的是,这正是 Java 的工作方式。

        如果这是“你的”代码,那么只需添加类似 sn-ps 的代码

        if (foo == null) {
          throw new NullPointerException("foo == null");
        }
        

        在分配 foo 之后。如果 foo 是参数,则在方法体的开头立即检查,并抛出 IllegalArgumentException。

        这应该可以帮助您澄清问题。

        【讨论】:

        • 我认为assert 语句比if 语句更可取。
        • 我会谋杀任何使用这样的代码进行代码审查的人。
        • @Thorbjorn 我相信用户代码对 NullPointerException 的使用是不受欢迎的。它是由 JRE 抛出的。
        • @Michael,我相信这仍然值得商榷。就我个人而言,我尝试编写代码以避免永远返回 null(对我来说是 NullObjects),我可能只会抛出一个 RuntimeException。在 THIS 特定设置中,我试图在提供更多信息的同时提供保留当前行为的建议(可能会捕获其他异常)。
        • @MichaelDonohue 不。JDK 充满了故意抛出 NPE 的代码,例如任何合理编写的 addListenet() 方法,非常希望在源头捕获错误,而不是让它出错在其他一些线程中可能会在几个小时后使用其他方法。
        【解决方案7】:

        您可以在Eclipse中调试时在空指针异常上添加断点,以获取异常的确切原因。

        【讨论】: