28

如果我在这样的调用中收到 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 告诉我要查找哪一行。

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

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

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

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

  • 如果它们不能返回 null,请随意链接它们。无需检查!= null,无需担心 NullPointerExceptions(请注意链接仍然违反 Demeter 法则,但我可以忍受
  • 如果它们可能返回 null,永远不要,永远不要链接它们,并检查每个可能返回 null 的 null 值

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

4

11 回答 11

16

NPE 是 Java 中最没用的异常。它似乎总是被懒惰地实现并且从不确切地说明是什么原因造成的,即使像“xyZ 类为空”这样简单,在调试这种情况下也会有很大帮助。

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

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

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

于 2009-01-05T06:33:49.287 回答
11

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

于 2009-01-04T12:33:05.287 回答
9

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

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

我会假设,你可以在 netbeans 或 eclipse 中做类似的事情。

编辑:是关于如何在 eclipse 中添加异常断点的解释

于 2009-01-04T12:35:11.383 回答
5

如果你发现自己经常写:

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

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

见得墨忒耳法则,其中说:

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

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

于 2009-01-04T17:27:34.117 回答
3

我通常不会在有多个可空 getter 的情况下像这样链接 getter。

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

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

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

于 2009-01-04T12:19:39.760 回答
2

早期失败也是一种选择。

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

public Foo getSomething()
{
  Foo result;
  ...
  if (result == null) {
    throw new IllegalStateException("Something is missing");
  }
  return result;
}
于 2009-01-04T12:35:18.670 回答
2

下面介绍如何使用 Eclipse 查找错误。

首先,在行上设置断点:

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

在调试模式下运行程序,当行被命中时允许调试器切换到它的透视图。

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

它是空的吗?你找到了你的 NPE。它是非空的吗?然后突出显示 someObject.getSomething() (包括括号)并检查它。它是空的吗?等等。继续沿着链条找出你的 NPE 发生在哪里,而不必更改你的代码。

于 2009-01-04T15:49:24.237 回答
1

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

基本上,如果 null 是一个有效的响应,你必须检查它。如果没有,请断言它(如果可以的话)。但无论您做什么,都应尽量减少 null 出于此原因而成为有效响应的情况。

于 2009-01-04T12:31:28.693 回答
1

如果你不得不分道扬镳或进行复杂的调试以发现问题,那么这通常是上帝告诉你代码没有足够早地检查空值的方式。

如果您有一个采用对象参数的方法或构造函数,并且所讨论的对象/方法无法合理地处理该参数为空,那么只需检查并在那里抛出 NullPointerException。

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

于 2009-01-04T20:07:27.947 回答
0

像这样的链式表达式对于 NullPointerExceptions(以及可能发生的大多数其他问题)来说是一种痛苦的调试,所以我建议你尽量避免它。不过,您可能已经听够了,就像之前提到的海报一样,您可以在实际的 NullPointerException 上添加断点以查看它发生的位置。

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

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

于 2009-01-04T14:36:22.310 回答
0

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

于 2009-01-04T17:07:52.363 回答