如何在getter链中跟踪NullPointerException

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

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

我得到一个相当无用的exception文本,如:

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

我发现很难找到实际上调用returend null,经常发现自己将代码重构为这样的东西:

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

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

你们中的一些人可能会认为,连接getter是一种糟糕的风格,无论如何都应该避免,但我的问题是:我可以在不更改代码的情况下找到错误吗?

提示:我正在使用eclipse,我知道调试器是什么,但我不知道如何将它应用于问题。

我对答案的结论是:
一些答案告诉我,我不应该一个接一个地连接吸气剂,一些答案显示我如何调试我的代码,如果我不赞成这个建议。

我已经接受了一个答案,它教会了我什么时候连锁吸气剂:

  • 如果他们不能返回null,只要你喜欢就链接它们。 无需检查!= null,无需担心NullPointerExceptions(请注意链接仍然是demeter的法则,但我可以忍受
  • 如果它们可能返回null,则不要永远不会链接它们,并对每个可能返回null的值执行空值检查

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

答案取决于你如何看待你的吸气者(合同)。 如果它们可能返回null那么每次都应该检查返回值。 如果getter不应该返回null ,那么getter应该包含一个检查并抛出一个exception( IllegalStateException ?)而不是返回null ,你承诺永远不会返回。 堆栈跟踪将指向您确切的getter。 你甚至可以在exception消息中找到意外状态你的getter。

NPE是Java中最无用的Exception期间。 它似乎总是懒惰地实现,并且永远不会确切地告诉它是什么导致它,即使像“类xyZ为空”这样简单也会在调试这种情况时提供很多帮助。

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

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

你有它,现在NPE指向正确的线,因此正确的方法,投掷实际的NPE。 不像我想要的那样优雅的解决方案,但它的工作原理。

在IntelliJ IDEA中,您可以设置exceptionbreakpoints 。 抛出指定的exception时会触发这些断点(您可以将其范围限定为包或类)。

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

我认为,你可以在netbeans或eclipse中做类似的事情。

编辑: 这是一个关于如何在eclipse中添加exceptionbreakpoint的解释

如果你发现自己经常写作:

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

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

另见德米特定律 ,其中说:

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

因此,不应该有一个消息链,例如a.getB().getC().doSomething() 。 遵循这个“法律”除了使NullPointerExceptions更容易调试之外还有许多其他好处。

我通常不会链接这样的吸气剂,因为有多个可空的吸气剂。

如果你在ide中运行,你可以设置一个断点,并在每个元素上连续使用ide的“evaluate expression”function。

但是,当您从生产服务器日志中收到此错误消息时,您将会摸不着头脑。 所以最好每行最多保留一个可以为空的项目。

同时我们可以梦想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()(包括括号)并检查它。 它是空的吗? 等等。继续沿着链条找出NPE发生的位置,而不必更改代码。

您可能想要参考有关避免!= null的问题 。

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

如果您必须达到分割线或进行精心调试以发现问题的程度,那么通常上帝会告诉您,您的代码并未及早检查空值。

如果你有一个方法或构造函数接受一个对象参数,并且有问题的对象/方法无法合理地处理该参数为null,那么只需检查并抛出NullPointerException然后。

我已经看到人们发明了“编码风格”规则来试图解决这个问题,例如“你不允许在一行上有一个以上的点”。 但这只是鼓励编程在错误的地方发现错误。

像这样的链接表达式很难调试NullPointerExceptions(以及可能发生的大多数其他问题)所以我建议你尝试避免它。 您可能已经听说过足够的内容,就像前面提到的海报一样,您可以在实际的NullPointerException上添加断点以查看它发生的位置。

在eclipse(和大多数IDE)中,您还可以使用监视表达式来评估在调试器中运行的代码。 您可以选择代码并使用contet菜单添加新手表。

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

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