程序中故意抛出NullPointerException是否可行?

60

当存在后置条件时,方法的返回值不能为 null,应该怎么办?

可以这样做:

assert returnValue != null : "Not acceptable null value";

但是断言可能会被关闭!

那么这样做可以吗?

if(returnValue==null)
      {
           throw new NullPointerException("return value is null at method AAA");
      }

还是使用用户定义的异常(例如NullReturnValueException)更好呢?


2
在使用assert或null检查之间的选择上:假设assert仅用于开发环境,并且assert仅是您或其他开发人员的合理性检查。另一方面,null检查适用于所有类型的客户端交互(无论是与用户还是其他人使用您的代码,例如公共API),并在所有环境中运行时运行。 - cthulhu
19个回答

72
我建议你永远不要自己抛出NullPointerException。主要原因是,正如Thorbjørn Ravn Andersen在下面的评论中所说,您不想将“真正的坏NPE”与故意抛出的NPE混淆在一起。因此,在您确信能够识别“有效”的NPE之前,建议您在要告知API用户null不是有效的参数值时使用IllegalArgumentException。应该记录非法null参数传递时方法的行为。
另一个(更现代的)选择是在参数附近使用@NotNull注释。这里有一个关于使用@NotNull注释的文章
正如我之前提到的,也可能存在情况,当抛出NPE对您或您的团队来说并不会产生困惑:NPE的原因应该是明确和可识别的。
例如,如果您使用带有先决条件模块的某个库,例如Guava,那么我认为使用类似checkNotNull()的方法处理非法传递的nulls是一种较好的方式。 checkNotNull(arg, msg)会抛出NPE,但从堆栈跟踪中可以清楚地看出,它是由Preconditions.checkNotNull()生成的,因此它不是未知错误,而是预期行为。

31
实际上,NullPointerException是一个很好的方式来告诉我们,某个参数不应该为空。参考《Effective Java》的说法:可以说,所有错误的方法调用都归结为非法的参数或状态,但其他异常通常用于某些特定类型的非法参数和状态。如果调用者在一些禁止使用null值的参数中传递了null,惯例规定应该抛出NullPointerException而不是IllegalArgumentException。 - whiskeysierra
15
看一下javadoc:应用程序应该抛出这个类的实例来指示对空对象的其他非法使用。 对我来说非常清楚。当传递null时,抛出NPE是合法和有效的,应该针对每个非空参数进行。没有例外。 - whiskeysierra
7
保持只有运行时抛出NullPointerException,而不是由你的代码抛出,可以更容易地确定一个NPE(空指针异常)的严重程度。 - Thorbjørn Ravn Andersen
3
我同意whiskeysierra的观点。NPE是指示某个方法参数为null的方式,也是最适合的异常之一,而不是IllegalArgumentException。在自己的代码中抛出它与在Java库代码中抛出NPE没有什么不同。我按照契约编程,因此我的方法的用户会得到如果他读了我的方法的Javadoc应该期望的结果。抛出它可以防止代码继续执行(尽早失败);否则,它将在与实际错误位置无关的地方失败。应始终认真对待NPE,因为它表示程序错误。 - Timmos
2
最终,这是关于软件工程的讨论。但是就使用代码来“模拟”行为而言,抛出NPE并不有帮助性,尽管文档中可能会这样写。我们可以听一下经验丰富的计算机科学家C. A. R. Hoare的建议(Tony Hoare)。他称空引用的发明是他的“10亿美元错误”。比起抛出NPE,更好的做法是告知调用者输入无效的原因。说明输入是null很好,但更好的做法是说该参数无效,因为null不是有效选项。 - Michael Plautz
显示剩余2条评论

44

我认为在JVM抛出NullPointerException之前,尽早抛出它没有问题——特别是对于空参数。虽然有一些关于这个问题的争议,但Java SE库中有很多例子都是这样做的。我不明白为什么NullPointerException在你无法自己抛出它的方面会变得神圣。

然而,我跑题了。这个问题是关于另外一件事情的。你在谈论一个后置条件,即返回值不能为null。当然,在这种情况下,null意味着你的方法内部有一个错误。

你怎么记录这个呢?"如果返回值意外地为空,则此方法会抛出NullPointerException"?没有解释如何发生这种情况吗?不,我会在这里使用断言。异常应该用于可能发生的错误,而不是覆盖方法内部如果有问题就可能发生的事情,因为那对任何人都没有帮助。


6
如果传入了空参数,则抛出IllegalArgumentException异常可能更为合适。NullPointerException通常用于指示对空值进行操作的尝试。如果您想要抛出NPE异常,则只需在方法中放弃对空值的检查,JVM自然会抛出NPE异常。 - Joshua Jones

30

考虑到在Java中,NullPointerException是表示意外的空值的惯用方式,我建议您抛出标准的NullPointerException而不是自定义的。同时,请记住,最小惊奇原则会建议您不要为系统异常已存在的情况发明自己的异常类型。

断言对于调试很有用,但如果必须处理某些条件,则不是处理错误条件的好方法。


16
NullPointerException的问题在于,当您忘记检查某些内容是否为null或者传递了错误的null参数时,就会出现这种异常情况。
根据我的经验,Java程序员很快就会学会,这种异常是由代码中的错误引起的,因此手动抛出它会让大多数人感到非常困惑。如果您传递了不可接受的参数(例如null,其中某些内容不应该为null),则使用IllegalArgumentException会更好。
这也触发了另一个启发式。NPE=代码中出错了,而IllegalArgumentException=给方法的对象无效。
另一方面,javadoc告诉我们:

应用程序应抛出此类的实例以指示
其他对null对象的非法使用。

因此,抛出NPE是合法的,但这并不是常规做法,因此我建议使用IllegalArgumentException

2
我认为那不正确。的确,“NullPointerException”意味着我忘记检查某些内容。但是,当它直接由方法抛出时,如果禁止传入“null”,则实际上存在错误。因此,唯一正确的方法是抛出NPE。 - kap
问题在于,如果按照这个问题和JDK中无数的例子故意抛出它,你的第一句话就不成立了。 - user207421
当您处于一个值为null的条件下,并且官方Java文档推荐时,抛出NPE是可以的。请查看以下链接:https://docs.oracle.com/javase/6/docs/api/java/lang/NullPointerException.html - Ranjit Soni
如果作为开发人员我收到了异常(未被捕获),通常会更关注异常消息而不是异常名称。我会使用Objects.requireNonNull,它会抛出NPE,但一定要包含一条消息,以便清楚地看到哪个对象为空。 - rjmunro

8

当然,并没有一项通用法律禁止抛出NullPointerException,但在这种抽象的例子中是否应该这样做是很难回答的。你不想让人们处于捕获NullPointerException的位置。像这样的代码(真实示例,我发誓):

catch (NullPointerException npe) {
  if (npe.getMessage().equals("Null return value from getProdByCode") {
    drawToUser("Unable to find a product for the product type code you entered");
  } 
}

如果返回值为空,那么这肯定是你做错了什么的明确指示。因此,如果空返回值是系统状态的指示,而你实际上可以进行通信,请使用可以传达该状态的异常。我很难想到有多少情况需要检查空引用只是为了抛出nullpointer。通常下一行代码会抛出nullpointer(或更详细的信息)!


这个特定的例子希望是由于一个空的ProdCode输入字段引起的吗? - Thorbjørn Ravn Andersen
如果只有这样,那至少还有点道理。这是我与一位合同开发人员合作时处理基于用户输入的搜索结果的方式。 - Affe

6
避免抛出NullPointerException异常,因为这会让大多数人误以为是虚拟机抛出的。考虑使用IllegalArgumentException异常代替,这将明确表明是程序员发起的异常。 点击此处了解更多信息。

1
无论如何,Oracle的文档说的是不同的内容。 http://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html 但我必须说我不同意以上文章中的所有内容。 - Gab
1
这里是否有理由让任意数量的人同意 SourceForge 项目上的任意观点?为什么大多数人都会假设某些事情,而实际的原因就在堆栈跟踪中呢? - user207421

5

如果你记得描述,我认为使用NullPointerException是可以的。这就是调查人员所要处理的内容(行号可能会发生变化)。还要记得在特殊情况下记录方法抛出的空指针异常。

如果你在开头检查方法参数,那么throw new IllegalArgumentException("foo==null")对我来说也是可以接受的。


4
如果您描述了一个方法契约,其中返回值不能为null,那么最好确保您不会返回null。但这根本不是空指针异常。如果您必须返回的值是null,那么显然调用者要么给出了错误的参数(IllegalArgumentException),要么您处于无效状态(IllegalStateException),或者发生了比空指针异常更有意义的其他异常情况(通常表示编程错误)。

3
我有一本书叫做O'Reilly's Java in A Nutshell,它是由一位专家撰写的,其中列出了NullPointerException的定义:

表示试图访问空对象的字段或调用其方法。

由于返回null不属于上述任何一种情况,因此我认为编写自己的异常会更合适。

2
仅仅因为某些书籍使用这个定义,并不意味着它是广泛接受的,特别是当它与该异常的JavaDoc相矛盾时。 - meriton
从Java文档中可以看到,当程序试图访问对象的字段或方法,或者数组的元素时,如果没有实例或数组可用,也就是说对象或数组指向{@code null},就会抛出此异常。在一些其他不太明显的情况下也会发生,例如在{@code throw e}语句中,Throwable引用为{@code null}的情况下。 - Rafe Kettler
2
在 Java 文档的哪里可以找到这个信息?该异常的 JavaDoc 结尾处写道:"应用程序应该抛出这个类的实例来表示对 null 对象的其他非法使用。" - meriton
你在看哪个JavaDoc?https://www.docjar.com/docs/api/java/lang/NullPointerException.html从JavaDoc中获取信息。你误解了从称为JavaDoc的网站获取的信息,实际上更像是一个维基百科。这些情况都与返回空值无关。 - Rafe Kettler
3
有趣的是,Apache Harmony项目选择提供与我链接的Sun JDK实现不同的javadoc文档(该链接不再由Sun托管,这实际上是Oracle的责任)。请注意,根据他们的网站,Apache Harmony并非Java的认证实现。事实上,他们不声称与之完全兼容。 - meriton

3

NullPointerException的JavaDoc中指出:

Thrown when an application attempts to use null in a case where an object is required. These include:

* Calling the instance method of a null object.
* Accessing or modifying the field of a null object.
* Taking the length of null as if it were an array.
* Accessing or modifying the slots of null as if it were an array.
* Throwing null as if it were a Throwable value. 

Applications should throw instances of this class to indicate other illegal uses of the null object.

我认为违反后置条件是一种非法行为。然而,我认为所使用的异常并不重要,因为我们正在谈论一个代码路径,它应该是(且希望是)无法到达的,因此您将没有针对该异常的特定错误处理,因此该名称的唯一影响是日志文件中某个条目的措辞略有不同,而这些日志文件可能永远不会被查看。
相反,如果您认为后置条件可能被违反,则将更多的调试信息包括进去可能是一个好主意,例如,调用该方法的参数。

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