【问题标题】:Find out what variable is throwing a NullPointerException programmatically找出以编程方式抛出 NullPointerException 的变量
【发布时间】:2011-02-09 16:58:59
【问题描述】:

我知道我可以使用这些技术找出 Java 中的变量是否为空:

  • 如果(var==null) -> 工作量太大
  • try { ... } catch (NullPointerException e) { ...} -> 它告诉我是哪一行抛出异常
  • 使用调试器 -> 手动,太慢了

考虑这行代码:

if (this.superSL.items.get(name).getSource().compareTo(VIsualShoppingList.Source_EXTRA)==0)  {

我想知道是否有一种通用的方法可以以编程方式找出在某个代码区域中抛出 NullPointerException 的变量(不仅仅是行)。在示例中,知道

【问题讨论】:

  • 嗯,如果这不是单行的话,会更容易找到 NullPointerException。这样的代码真的很难遵循。
  • 考虑到您的选择,我只想指出,从长远来看,“工作量太大”实际上可能意味着工作量要少得多。
  • 把它变成一个合适的块。您很快就会发现问题。
  • 不要忘记使用异常作为控制流机制是一件非常糟糕的事情......
  • 10 年后,JEP 358 将有所帮助:请参阅 my answer below

标签: java debugging nullpointerexception


【解决方案1】:

即使不涉及变量也可能导致空指针异常:

throw new NullPointerException();

我不得不说,没有通用的方法来确定特定变量的空指针异常。

最好的办法是在每一行中放置尽可能少的语句,以便清楚导致空指针异常的原因。考虑将问题中的代码重构为如下所示:

List items = this.superSL.items;
String name = items.get(name);
String source = name.getSource();
if (source.compareTo(VIsualShoppingList.Source_EXTRA) == 0)  {
    // ...
}

可以肯定的是,代码行数更多。但它也更具可读性和可维护性。

【讨论】:

  • 是否有语言可以告诉您哪个变量负责? (或 IDE)?
【解决方案2】:

您可能会考虑,作为announced here,JDK 14 应该包含the JEP 358

JEP 358:有用的 NullPointerExceptions

假设在这段代码中出现了 NPE:

a.b.c.i = 99;

文件名和行号不能准确指出哪个变量为空。
a 还是b 还是c

数组访问和赋值也会出现类似的问题。假设在这段代码中出现了 NPE:

a[i][j][k] = 99;

文件名和行号不能准确指出哪个数组组件为空。
a 还是a[i] 还是a[i][j]

说明:

如果更复杂的语句 a.b.c.i = 99; 抛出 NPE,则消息将剖析语句并通过显示导致 null 的完整访问路径来查明原因:

Exception in thread "main" java.lang.NullPointerException: 
        Cannot read field "c" because "a.b" is null
    at Prog.main(Prog.java:5)

再次:将使用 JDK 14 进行测试。


Holger 添加in the comments

对于表达式a[i][j][k],也有可能ijk 的类型为Integer 并且为空,因此拆箱失败。

在现实生活场景中,= 右侧的表达式也可能具有 NPE 的潜力。

我试过 jdk-14.0.1

它有效;它会产生类似

的消息
Cannot invoke "java.lang.Integer.intValue()" because "i" is null then.

当方法在没有调试信息的情况下编译时,它将使用“<local8>”而不是“i”,但这是不可避免的。

【讨论】:

  • 对于表达式a[i][j][k],也有可能ijk的类型为Integer并且是null,因此拆箱失败。在现实生活场景中,= 右侧的表达式也可能具有 NPE 的潜力。
  • @Holger 在这种情况下,JEP 358 会检测到(我为空)吗?
  • 我没有尝试,但是因为这很容易在执行中发现(原因是在 null 引用上调用 intValue())并且确定子表达式并没有什么不同识别,例如a[i]null,我期待这个。我想,我会在今天晚些时候试一试。
  • 刚刚验证它有效;然后它会产生类似Cannot invoke "java.lang.Integer.intValue()" because "i" is null 的消息。当方法在没有调试信息的情况下编译时,它将使用"<local8>" 而不是"i",但这是不可避免的。
  • @Holger 这似乎比以前的 JDK 版本更精确/准确。
【解决方案3】:

抱歉,不,没有一种简单的编程方式来确定哪个变量或方法调用是异常的来源。你可以使用Aspect-Oriented Programming (AOP) 之类的东西,例如AspectJ,但这不是语言固有的,通常不会仅仅出于调试目的而合并到程序中。

  • if (var==null) -> too much work
  • try { } catch() { }
  • Debugger

我知道你不想听到这个,但这只是做生意的成本。

if (this.superSL.items.get(name).getSource().compareTo(VIsualShoppingList.Source_EXTRA)==0)  {

看到这么多串在一起的方法调用是不寻常的。我相信你最好的选择是养成更多地分解这些的习惯 - 不必降低到每行 1 个调用,但比这更少。为什么?

1) Correctness - 在设计中这些调用之一返回 null 是否有效?如果是这样,您应该将其分解,测试并适当处理。

2) Understandability - 未来的维护者(包括未来的你)会更容易理解你是否中间,命名良好的变量,以帮助澄清这一行发生的事情。

3) Efficiency - 通常当您深入到图表中(将一系列方法调用串在一起)时,您可能需要稍后再回到那里。在中间变量中捕获这个中间值意味着避免再次调用一个或多个方法。

4) Debugging - 如您的问题所示,将这样的复杂行拆分可以简化调试。通过缩小异常的可能来源。

【讨论】:

    【解决方案4】:

    “手动使用调试器 -> 太慢”是什么意思?如果您的代码结构正确,那么在同一行中使用的变量不会超过两个或三个。检查它们这么慢吗?你不是每分钟都有 NullPointers。

    【讨论】:

    • > “手动使用调试器 -> 太慢了”是什么意思? - 例如,根据我的经验,当您必须远程调试时。但有时甚至无法连接远程调试器。
    【解决方案5】:

    我知道你建议 (var==null) 工作量太大,但是,正如 Miguel 在 cmets 中所说,我会这样做。

    【讨论】:

      【解决方案6】:

      我想你应该注意到Demeters Law

      严格遵守它的人并不多,因为它导致了很多委托方法。
      但是离它太远会导致对应该透明的内部结构的依赖。

      【讨论】:

      • 只是因为东西串在一起,并不意味着你违反了得墨忒耳定律。主要是当您通过链接获取参考,然后修改某些内容时。向下以只读方式显示或比较值并没有错。
      【解决方案7】:

      不幸的是,Java 不会向您显示变量的名称或错误的确切位置,而不是行号。但是,如果您使用 Eclipse,则可以使用可为空的注释,例如,请参阅 http://www.fosslc.org/drupal/content/bye-bye-npe。有关其他注释系统,请参阅 Which @NotNull Java annotation should I use?

      【讨论】:

        【解决方案8】:

        对我来说真正有用的是捕获通常抛出的异常,然后使用 Log 来查看它们中的任何一个是否具有“null”值。

        我的代码:

         try {
                         if (description_visible) advice_title_cur.setText(all_title_array[pos]);
                         else advice_title_cur.setText(all_title_array[pos] + "...");
        
                     } catch (NullPointerException e) {
                         e.printStackTrace();
                         Log.e("My name", "description_visible " + description_visible);
                         Log.e("My name", "advice_title_cur " + advice_title_cur);
                         Log.e("My name", "all_title_array " + all_title_array);
                         Log.e("My name", "pos " + pos);
                     }
        

        【讨论】:

          猜你喜欢
          • 2011-03-20
          • 2010-11-15
          • 2012-07-03
          • 2015-12-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-04
          • 1970-01-01
          相关资源
          最近更新 更多