我应该显式地抛出NullPointerException还是让Java代替我抛出?

14
正如标题所说,我想知道关于抛出NullPointerException的最佳实践是什么。具体来说,如果我有一个外部库函数,在某些情况下可以返回null,而我不想真正处理这些情况(请参见下面的特定示例),因为null表示软件存在问题。问题是,我应该:
  1. 检查返回值是否为null并自己抛出NullPointerException,或者
  2. 当我尝试使用对象时,是否应该让Java为我处理。
第一种方法让我能够添加一些额外的信息,因为我可以构造NullPointerException,但是第二种方法在我看来会使代码更简洁。我还想知道任何性能影响,也就是说,Java在本地抛出NPE会更有效吗?
例如,我正在尝试使用Java语音API创建语音合成器,使用以下代码:
synthesizer = Central.createSynthesizer(generalDesc);

if (synthesizer == null) {
    // (1) throw NPE explicitly
    throw new NullPointerException("No general domain synthesizer found.");
}

// (2) let the JVM throw the NPE when dereferencing
synthesizer.allocate();
如果Central.createSynthesizer 找不到合适的语音合成器,通常是由于缺少speech.properties文件所导致的,此时它会返回null。因此,这是系统设置错误造成的问题,并且很难在运行时进行恢复,而不是需要以编程方式处理的情况。因此,我认为抛出NullPointerException是一个有效的响应,因为它指示了一个错误(不在代码中,而在软件部署中)。但是,由于synthesizer对象在下一条语句中被取消引用,所以我是否应该让JVM为我抛出NPE并保存空值检查呢? 补充说明:考虑到speech.properties 在JVM启动时需要存在于文件系统中(通常是"user.home" 或者 "java.home/lib"),当createSynthesizer找不到它而返回null时,这很令人费解。我认为,在这里抛出NullPointerException是正确的做法,因为它表明了软件部署中的实际错误。
6个回答

8

我认为你永远不应该明确地创建一个NullPointerException,而应使用更清晰描述情况的异常类型。在你的情况下,我会选择IllegalStateException,因为它适用于"系统设置错误,在运行时几乎无法恢复"的情况。或者你可以创建自己的ComponentMissingException。在必需的方法参数为空时,通常使用IllegalArgumentException


1
在方法需要的参数为 null 的情况下,通常会使用 IllegalArgumentException。但是,在我看来,如果语言中有一个扩展 IllegalArgumentException 的 NullArgumentException,将会更直观。 - Mister Smith
方法参数没问题。Java的Central类会在"user.home"或"java.home/lib"中寻找speech.properties文件,而不是从参数中获取位置。这通常作为安装语音引擎的一部分来完成,因此实际上是一个部署问题。IllegalStateException可能确实是适当的,但我也想看看对于首次出现该异常时如何处理的共识是什么。在我的观点中,抛出IllegalStateException就是在处理它,因为我必须积极地编写代码来处理它,而不是只让JVM抛出NPE。 - ThomasH

8
在你的情况下:两者都不是。检查null并抛出更有意义的异常,而不是NPE。
一般来说,如果不应该出现NPE,则不需要显式测试它,Java会为您执行此操作。写更少的测试,阅读更少的代码,分析更少的复杂性。
但是,如果预期为null,请尽快进行测试并进行相应解释。否则,NullPointerException将在稍后的不同行/方法中发生,使调试真正的问题更加困难。

我接受了这个答案,因为它背后的推理。当然,最好的做法是尽早抛出异常,但IllegalStateException可能是更合适的异常。然而,如果有人有令人信服的答案说明在某些情况下我不应该处理这个异常(例如,因为它表示一个错误,你不应该编写处理错误的代码,而是修复错误),我愿意改变我的答案 :) - ThomasH
1
@ThomasH 我不建议使用IllegalStateException。检查参数之前做任何其他操作的整个目的是为了防止修改对象状态。IllegalStateException应该发生在对象已经移动到不应该存在的状态时。通过在修改状态之前验证输入,可以防止非法状态发生。相反,我建议使用IllegalArgumentException。 - corsiKa
1
@glowcoder 对象确实已经移动到了不应该存在的状态。该方法返回 null 不是因为参数错误,而是因为在主机系统上找不到底层语音合成器引擎。这只会发生在引擎没有正确安装的情况下,并且是我代码外部的因素。这可能类似于对本地库的调用,当库没有正确安装时。 - ThomasH
@Thomas 啊,是的。那个库的实现者做了一个糟糕的决定。 - corsiKa
我不认为有必要发明一个不同的异常类。这个异常是NPE。 如果是同一个异常,那么就没有必要显式地测试null,NPE会被抛出。 - Tjunkie

3
我不喜欢把null作为一个有效的返回值,即使在“特殊情况”下也是如此。因此我采取了另一种方法。
在你的情况下,我会使用@NotNull注解来标记方法createSynthesizer(...)(@NotNull是一个非常好的注解)。如果createSynthesizer(...)想要返回null,那么我会立即得到一个IllegalStateException而不是NPE(NullPointerException)。
你会收到以下内容:
java.lang.IllegalStateException: @NotNull method .../.../createSynthetiser(...) must not return null

这种方法有几个好处:
  • 既然NullPointerExceptionIllegalStateException都扩展自RuntimeException,所以您不会根本性地改变程序。

  • 异常将在出错时立即抛出(而不是稍后,在检查/抛出自己或尝试取消引用null时)。

  • 您不再需要费心编写if ... == null / throw这部分内容。

作为一个巨大的附加好处,一些IDE(如IntelliJ IDEA)将实时警告您可能存在的@NotNull违规。


一些IDE(例如IntelliJ IDEA)实际上只有IDEA,因为@NotNull是IDEA的扩展。据我所知,在标准Java中不存在这种情况。抛出IllegalStateException的行为在标准Java中肯定不存在,我怀疑这也不仅仅是IDEA的事情。您是否正在运行某种AOP? - Tom Anderson
@Tom Anderson:在这种情况下抛出IllegalStateException是完全有道理的,而且正是Michael B.在他的回答中建议的其中之一。那么我不知道你所说的“IDEA扩展”是什么意思。NotNull是Java 1.5注释,您可以在任何Java程序中使用它:它是一个微小的*.jar*文件。使用这样的注释的好处是如此巨大,以至于真的没有理由不使用。但是,诚然,实时检查可能只在IDEA下可行,但这并不使NotNull不是超级有用的。 - Cedric Martin
Central.createSynthesizer 是 Java Speech API 中的一个方法,不在我的控制范围内,因此我无法对其进行注释。但是,立即抛出异常值得赞赏,尽管我接受了 Tomasz 的答案,因为他的回答仅讨论了立即抛出异常的原因。 - ThomasH
@Tom Anderson:顺便说一下,我们没有运行任何AOP。如果一个不应该返回* null *的方法确实返回了null,很容易就可以认为程序处于非法状态,因此ISEx是有意义的(再次提醒,抛出ISEx是Michael B.建议的其中之一)。也有人认为,最终几乎所有异常都归结为非法状态或非法参数。在一个不太相关的侧面上,IntelliJ IDEA可能是有史以来编写的最好的Swing应用程序,我们可能需要从JetBrains的非常聪明的大脑中学习一些东西;) - Cedric Martin
Cedric:冷静点!我喜欢@NotNull,我认为这是一个很好的想法,并且自动检查它很酷。我也不反对在这里使用IllegalStateException作为合理的异常。当然,我尊重IDEA,这是一个伟大的IDE。但是,老实说,@NotNull不是标准Java的一部分,而且运行时检查必须需要某种AOP或其他魔法。如果您持有不同意见,我很乐意看到一个小的代码示例,我可以在命令行上编译和运行,而无需任何额外的库。 - Tom Anderson
@Tom Anderson:哎呀,抱歉,我不是故意不冷静的 :) 我以为你问我们这边是否有做任何特殊处理,但实际上我们没有。话虽如此,我们的构建过程正在重复使用由IntelliJ编译的类,所以我想那就是AOP(或其他什么东西)所在的地方。(顺便说一句,我们正在重用由IntelliJ编译的类,因为我们正在使用他们的GUI构建器)。 - Cedric Martin

1
关于原始代码:
synthesizer = Central.createSynthesizer(generalDesc);

if (synthesizer == null) {
    // (1) throw NPE explicitly
    throw new NullPointerException("No general domain synthesizer found.");
}

// (2) let the JVM throw the NPE when dereferencing
synthesizer.allocate();

我认为按照这里所示的方式抛出NPE是可以的,但需要极大的注意事项。只有在NPE构造函数参数足够描述性(并且希望是唯一的)消息(最好是从常量或资源集中提取的消息)的情况下才可以这样做。另一个警告是你的主要优先事项是让事情完成,如果情况如此,那么这将算作一种可接受的快速解决方案。

在理想情况下,我的首选是使用特定于无效配置的异常。当这些不可用时,要么使用NPE子类(例如Apache Commons Math的NullArgumentException),要么使用Apache Common Lang 2.x中找到的旧异常。这是我对NPE和IllegalArgument类型异常的立场。我并不一定同意Apache Common Lang关于更喜欢使用标准JDK异常而不是更语义相关的异常的position。但这只是我的看法,我要结束这个话题了...

...所以回到最初的问题。正如我之前所说,像这样抛出NPE在快速而肮脏的方式下是可以的(当你处于那种“需要把那些狗屎搞定!!(10+1)”的情况下)。

然而,请注意,这是由应用程序或系统配置问题引起的NPE,正如您正确地识别的那样。也就是说,NPE不是根本原因,而是另一个错误条件的症状或影响(在这种情况下,是配置或环境错误)。

如果Central.createSynthesizer找不到合适的语音合成器,则返回null,这往往是由于缺少speech.properties文件引起的。因此,这是系统设置错误,而且在运行时相当难以恢复,而不是需要以编程方式处理的情况。

无法恢复并不是一定的。应用程序可能有其他方法来以编程方式处理这种情况。

因此,我认为抛出NullPointerException是一个有效的响应,因为它表示存在错误(不在代码中,而是在软件部署中)。但由于合成器对象在下一条语句中被取消引用,所以我应该让JVM为我抛出NPE并保存空检查吗?
即使在“快点把这个东西拿出去”的情况下,我也不会这样做。JVM抛出的NPE将具有非常不具信息性的消息。通常,在遇到NULL时检查所有内容并抛出一个描述性异常(NPE或其他异常)。
如果您可以保证已经检查了任何您正在获取的内容(例如参数),请不要检查NPE(以设计契约的方式)。
附加说明:考虑到 JVM 加载 speech.properties 文件时需要存在于文件系统中(通常是“user.home”或“java.home/lib”),当 createSynthesizer 方法无法找到该文件时,它没有直接抛出 NPE(这是我最初由于口误写下的内容),而是返回 null,这令人困惑。
再次强调,这是因为对于这种情况的响应是特定于应用程序的。应用程序可能决定进入部分功能的“跛脚”模式,而不是崩溃。如果 createSynthesizer 抛出 NPE,则 API 设计者会强制应用程序设计者采用后一种行为,或者更费力地实现“跛脚”操作模式(通过使用 catch/try 而不是简单的 if-null 测试)。
我认为在这里抛出 NullPointerException 是正确的做法,因为它表示软件部署中的实际错误。
同样地,只有在 NPE 是快速解决问题的临时方案时才是可以的。在这些条件下,它是可以接受的。更好的方法是识别这是一个配置错误。

因此,最好使用特定于应用程序的异常,例如IllegalConfigurationExceptionInvalidConfigurationExceptionIncompleteConfigurationException。我不喜欢在这种情况下使用java.lang.IllegalStateException,因为这不是由于在无效状态下调用某些内容引起的。无效状态是由于无效配置而达到的。也许我在玩语义游戏,但在这种情况下使用IllegalStateException有点棘手(我知道这只是我的主观看法)。


0

我从未使用过Java,但如果我正在使用该应用程序,我希望看到错误消息而不是崩溃。NullPointerException对我来说听起来像是代码中的一个错误 - 我宁愿看到一条带有如何正确配置程序的指示的错误消息,或者至少是一个链接到网页的超链接,该网页包含这些指示。

如果我是用户,并且看到程序已经终止并出现了NullPointerException,我会向程序提交错误报告,或者至少会感到困惑,不知道下一步该怎么做。


0

非常有趣的问题。

我认为该方法应该抛出某种ProblemCreatingSynthesizerException,而不是返回null。

我会放置空值检查并抛出NPE或其他自定义ProblemWithSynthesizerException(Sun出于某种原因将此异常构思为JVM-ish异常,不适用于程序员使用。这是一些认证教程和书籍中所说的。然而,我不相信这一点,有时我在我的库中抛出自己的NPEs)。


抱歉,我在原问题陈述中有一个错误。实际上,Central.createSynthesizer返回null,这就是我一开始遇到困境的原因。只有在软件部署出现问题(即:语音引擎安装有误)时才会发生这种情况。对于造成的混淆,我感到非常抱歉。 - ThomasH
抛出 BadSynthesizerConfigurationException 异常? - Demi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接