如果我在这样的调用中收到 NullPointerException:

someObject.getSomething().getSomethingElse().
    getAnotherThing().getYetAnotherObject().getValue();

我收到一个相当无用的异常文本,例如:

Exception in thread "main" java.lang.NullPointerException
at package.SomeClass.someMethod(SomeClass.java:12)

我发现很难找出哪个调用实际上返回 null,经常发现自己将代码重构为如下所示:

Foo ret1 = someObject.getSomething();
Bar ret2 = ret1.getSomethingElse();
Baz ret3 = ret2.getAnotherThing();
Bam ret4 = ret3.getYetAnotherOject();
int ret5 = ret4.getValue();

然后等待更具描述性的 NullPointerException 告诉我要查找哪一行。

你们中的一些人可能会认为串联吸气剂是一种不好的风格,无论如何都应该避免,但我的问题是:不改代码能找到bug吗?

暗示:我正在使用 eclipse 并且我知道调试器是什么,但我无法弄清楚如何将其应用于问题。

我对答案的结论:
一些答案告诉我,我不应该一个接一个地链接吸气剂,一些答案告诉我,如果我不遵守该建议,如何调试我的代码。

我已经接受了一个答案,它教会了我何时链接吸气剂:

  • 如果它们不能返回 null,则可以将它们链接起来,只要您愿意。无需检查 != null,无需担心 NullPointerExceptions (请注意,链接仍然违反德米特法则,但我可以接受)
  • 如果它们可能返回 null,则永远不要链接它们,并对每个可能返回 null 的值执行 null 值检查

这使得任何关于实际调试的好的建议都毫无用处。

有帮助吗?

解决方案

答案取决于您如何看待吸气剂(的合同)。如果他们可以回来 null 你真的应该每次都检查返回值。如果 getter 不应该返回 null, ,getter 应该包含一个检查并抛出一个异常(IllegalStateException?)而不是返回 null, ,你发誓永远不会回来。堆栈跟踪将向您指出确切的 getter。您甚至可以将 getter 发现的意外状态放入异常消息中。

其他提示

NPE 是 Java 中最无用的异常。它似乎总是被懒惰地实现,并且从不确切地说明导致它的原因,即使像“class x.y.Z is null”这样简单也会对调试此类情况有很大帮助。

无论如何,我发现在这些情况下找到 NPE 抛出者的唯一好方法是以下类型的重构:

someObject.getSomething()
          .getSomethingElse()
          .getAnotherThing()
          .getYetAnotherObject()
          .getValue();

现在你已经知道了,现在 NPE 指向正确的线,从而正确的方法抛出了实际的 NPE。不像我想要的那样优雅的解决方案,但它有效。

在 IntelliJ IDEA 中您可以设置 异常断点. 。每当抛出指定的异常时,这些断点就会触发(您可以将其范围限制为包或类)。

这样就可以很容易地找到 NPE 的来源。

我假设,您可以在 netbeans 或 eclipse 中执行类似的操作。

编辑: 这里 是关于如何在 eclipse 中添加异常断点的说明

如果你发现自己经常写:

a.getB().getC().getD().getE();

这可能是代码异味,应该避免。例如,您可以重构为 a.getE() 哪个调用 b.getE() 哪个调用 c.getE() 哪个调用 d.getE(). 。(此示例对于您的特定用例可能没有意义,但它是修复此代码气味的一种模式。)

另请参阅 德墨忒耳定律, ,其中说:

  • 您的方法可以直接调用其类中的其他方法
  • 您的方法可以直接调用其自己的字段上的方法(但不能调用字段的字段上的方法)
  • 当您的方法接受参数时,您的方法可以直接调用这些参数上的方法。
  • 当您的方法创建本地对象时,该方法可以调用本地对象上的方法。

因此,一个人不应该有一系列消息,例如 a.getB().getC().doSomething(). 。除了使 NullPointerException 更易于调试之外,遵循这条“法则”还有更多好处。

当有多个可为 null 的 getter 时,我通常不会像这样链接 getter。

如果您在 ide 中运行,您只需设置一个断点并在每个元素上连续使用 ide 的“评估表达式”功能即可。

但是,当您从生产服务器日志中收到此错误消息时,您将会感到困惑。因此,最好每行最多保留一个可为空的项目。

同时我们可以梦想 groovy 的安全导航操作符

早期失败也是一种选择。

在代码中任何可以返回 null 值的地方,请考虑引入对 null 返回值的检查。

public Foo getSomething()
{
  Foo result;
  ...
  if (result == null) {
    throw new IllegalStateException("Something is missing");
  }
  return result;
}

以下是如何使用 Eclipse 查找错误。

首先,在该行设置断点:

someObject.getSomething().getSomethingElse().
getAnotherThing().getYetAnotherObject().getValue();

在调试模式下运行程序,允许调试器在命中该行时切换到其视角。

现在,突出显示“someObject”并按 CTRL+SHIFT+I(或右键单击并说“inspect”)。

是否为空?您已找到您的 NPE。它是非空的吗?然后突出显示 someObject.getSomething() (包括括号)并检查它。是否为空?ETC。继续沿着链向下找出 NPE 发生的位置,而无需更改代码。

您可能想参考 这个关于避免 != null 的问题.

基本上,如果 null 是有效响应,则必须检查它。如果没有,请断言它(如果可以的话)。但无论您做什么,请尝试尽量减少由于此原因以及其他原因而 null 是有效响应的情况。

如果您必须拆分线路或进行详细的调试才能发现问题,那么这通常是上帝告诉您代码没有足够早地检查 null 的方式。

如果您有一个接受对象参数的方法或构造函数,并且所讨论的对象/方法无法明智地处理该参数为空,那么只需检查并抛出 NullPointerException 即可。

我见过人们发明“编码风格”规则来尝试解决这个问题,例如“一行上不允许有多个点”。但这只会鼓励编程将错误发现在错误的位置。

像这样的链式表达式很难调试 NullPointerExceptions(以及可能发生的大多数其他问题),因此我建议您尝试避免它。您可能已经听够了,就像之前的海报提到的那样,您可以在实际的 NullPointerException 上添加断点来查看它发生的位置。

在 Eclipse(以及大多数 IDE)中,您还可以使用监视表达式来评估调试器中运行的代码。您可以通过选择代码并使用内容菜单添加新手表来执行此操作。

如果您可以控制返回 null 的方法,并且 null 是要返回的有效值,那么您还可以考虑 Null 对象模式。

将每个 getter 放在自己的行上并进行调试。单步执行 (F6) 每个方法以查找哪个调用返回 null

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top