关于java:null参数的IllegalArgumentException或NullPointerException?

IllegalArgumentException or NullPointerException for a null parameter?

我有一个简单的属性setter方法,null不适合这个特定的属性。在这种情况下,我总是很痛苦:我应该扔一个IllegalArgumentException还是一个NullPointerException?从javadocs看来,两者都是合适的。有什么可以理解的标准吗?或者这只是你应该做的事情中的一件,你想做什么都是正确的?


您应该使用IllegalArgumentException(IAE),而不是NullPointerException(NPE),原因如下:

首先,npe javadoc明确列出了npe适合的情况。注意,当null使用不当时,运行时会抛出所有这些代码。相反,IAE JavaDoc不能更清楚地说:"抛出来表示方法已经通过了非法或不适当的参数。"是的,就是你!

第二,当您在堆栈跟踪中看到一个NPE时,您会假设什么?可能是有人取消了对一个null的引用。当您看到IAE时,您假定堆栈顶部方法的调用方传入了一个非法值。同样,后者的假设是正确的,前者是误导性的。

第三,由于IAE是为验证参数而设计的,所以您必须假设它是默认的例外选择,那么为什么您要选择NPE呢?当然不是针对不同的行为——您真的希望调用代码与IAE分开捕获NPE,并因此做一些不同的事情吗?您是否试图传达更具体的错误消息?但无论如何,您可以在异常消息文本中这样做,因为对于所有其他不正确的参数,您应该这样做。

第四,所有其他不正确的参数数据都是依斯克拉汽车电器有限公司的,为什么不一致呢?为什么一个非法的null是如此特殊,以至于它应该从所有其他类型的非法论据中得到一个单独的例外?

最后,我接受JAVA API部分使用这种方式使用NPE的其他答案给出的参数。但是,Java API与从异常类型到命名约定的任何东西都不一致,所以我认为只是盲目复制(您最喜欢的部分)Java API并不是一个足够好的论点来胜过其他的考虑因素。


如果不希望null是一个允许值,则似乎需要一个IllegalArgumentException,如果试图使用一个结果为null的变量,则会抛出NullPointerException


标准是投掷NullPointerException。一般可靠的"有效Java"在项目42(第一版)、项目60(第二版)或项目72(第三版)中简要讨论了"赞成使用标准例外":

"Arguably, all erroneous method
invocations boil down to an illegal
argument or illegal state, but other
exceptions are standardly used for
certain kinds of illegal arguments and
states. If a caller passes null in
some parameter for which null values
are prohibited, convention dictates
that NullPointerException be thrown
rather than IllegalArgumentException."


我一直赞成将EDOCX1 0的参数用于空参数,直到今天,当我注意到Java 7中的EDCOX1 1Ω方法时。用这种方法,而不是:

1
2
3
if (param == null) {
    throw new IllegalArgumentException("param cannot be null.");
}

你可以做到:

1
Objects.requireNonNull(param);

如果你传递的参数是null,它将抛出一个NullPointerException

假设该方法是在EDOCX1中的右边Bang.Of 4,我认为它的存在是一个非常有力的指示,即抛出EDCOX1×2是"Java的做事方式"。

我想我已经决定了。

请注意,关于硬调试的论点是假的,因为您当然可以向NullPointerException提供一条消息,说明什么是空的以及为什么它不应该是空的。就像对IllegalArgumentException一样。

NullPointerException的一个额外的优点是,在高性能关键代码中,您可以不必显式地检查空值(和一个带有友好错误消息的NullPointerException),只需依赖于NullPointerException,当您调用一个空值参数的方法时,您将自动得到。如果您快速调用一个方法(即快速失败),那么您的效果基本上是相同的,只是对开发人员来说不太友好。大多数情况下,最好是显式检查并抛出一条有用的消息来指示哪个参数是空的,但是如果性能指示而不破坏方法/构造函数的已发布约定,则可以选择更改它。


我倾向于遵循JDK库的设计,尤其是集合和并发(JoshuaBloch、DougLea,这些人知道如何设计可靠的API)。无论如何,JDK中的许多API都主动抛出NullPointerException

例如,用于Map.containsKey的javadoc声明:

@throws NullPointerException if the key is null and this map
does not permit null keys (optional).

扔自己的NPE是完全正确的。约定是在异常消息中包含空的参数名称。

模式是:

1
2
3
4
5
public void someMethod(Object mustNotBeNull) {  
    if (mustNotBeNull == null) {  
        throw new NullPointerException("mustNotBeNull must not be null");  
    }  
}

无论您做什么,都不要允许设置一个坏值,并在以后其他代码尝试使用它时抛出异常。这使得调试成为一场噩梦。你应该始终遵循"快速失败"的原则。


投票支持杰森·科恩的论点,因为它得到了很好的呈现。让我一步一步地把它肢解。;-)

  • npe javadoc显式地说,"空对象的其他非法使用"。如果它仅限于运行时遇到空值而不应该遇到空值的情况,那么可以更简洁地定义所有这些情况。

  • 如果您假定错误的事情,这是无济于事的,但是假设正确地应用了封装,那么您真的不应该关心或注意是否错误地取消了对空的引用,而不是方法是否检测到不适当的空并触发了异常。

  • 我会选择NPE而不是IAE,原因有很多

    • 更具体的是非法经营的性质
    • 错误地允许空值的逻辑往往与错误地允许非法值的逻辑非常不同。例如,如果我正在验证用户输入的数据,如果我得到了不可接受的值,那么这个错误的来源就是应用程序的最终用户。如果我得到一个空值,那就是程序员错误。
    • 无效值可能导致堆栈溢出、内存不足错误、分析异常等。实际上,大多数错误在某些时候通常作为无效值出现在某些方法调用中。因此,我认为IAE实际上是runtimeexception下所有异常中最一般的一个。
  • 实际上,其他无效参数可能导致各种其他异常。UnknownHostException、FileNotFoundException、各种语法错误异常、IndexOutOfBoundsException、身份验证失败等。

总的来说,我觉得NPE受到了很大的指责,因为传统上它与未能遵循快速失败原则的代码相关。再加上JDK未能用消息字符串填充NPE,真的造成了一种强烈的负面情绪,这是没有充分根据的。实际上,从运行时的角度来看,NPE和IAE之间的区别是严格的名称。从这个角度来看,你对名字的理解越精确,你对来电者的理解就越清晰。


这是一个"圣战"式的问题。换句话说,这两种选择都是好的,但人们会有他们的偏好,他们会捍卫到死亡。


如果这是一个setter方法,并且null正在传递给它,我认为抛出IllegalArgumentException会更有意义。在实际使用null的情况下,NullPointerException似乎更有意义。

所以,如果你用的是nullNullPointer。如果是传进来的,是nullIllegalArgument


ApacheCommonsLang有一个NullArgumentException,它执行了这里讨论的许多事情:它扩展了IllegalArgumentException,并且它的唯一构造函数使用了应该为非空的参数的名称。

虽然我觉得抛出NullArgumentException或IllegalArgumentException之类的东西更准确地描述了异常情况,但我和同事们还是选择了遵从布洛赫关于这个问题的建议。


对所说的话再同意不过了。及早失败,快速失败。非常好的例外咒语。

关于抛出哪一个异常的问题主要是个人品味的问题。在我看来,IllegalArgumentException比使用NPE更具体,因为它告诉我问题出在我传递给方法的参数上,而不是在执行方法时生成的值上。

我的2分钱


实际上,抛出非法的AgulMutExtExpReor或Null PoExtExchange的问题在我看来只是少数人的"神圣战争",对Java中的异常处理有着难以理解的理解。一般来说,规则很简单,如下所示:

  • 必须尽快指示参数约束冲突(->fast fail),以避免更难调试的非法状态。
  • 如果空指针因任何原因无效,则引发NullPointerException
  • 如果数组/集合索引非法,则抛出arrayIndexOutOfBounds
  • 如果数组/集合大小为负,则引发NegativeArraySizeException
  • 如果存在上述未涵盖的非法参数,并且您没有其他更具体的异常类型,则将IllegalArgumentException作为废纸篓抛出。
  • 另一方面,如果某个字段中的约束冲突由于某些有效原因而无法通过快速失败避免,则捕获并重新引发非法状态异常或更具体的检查异常。在这种情况下,绝不允许传递原始的NullPointerException、ArrayIndexOutOfBounds等!

至少有三个非常好的理由反对将所有类型的参数约束违反映射到IllegalArgumentException,第三个理由可能非常严重,以致于标记实践的不良风格:

(1)程序员不能安全地假定所有违反参数约束的情况都会导致IllegalArgumentException,因为如果没有更具体的异常类型,大多数标准类都将此异常用作废纸篓。尝试将所有违反参数约束的情况映射到API中的IllegalArgumentException只会导致程序员对使用类感到沮丧,因为标准库大多遵循违反您的规则的不同规则,而且大多数API用户也会使用它们!

(2)映射异常实际上会导致由单一继承引起的异常类型:所有Java异常都是类,因此只支持单继承。因此,无法创建一个异常,也就是说,空指针异常和非法参数异常,因为子类只能从一个或另一个继承。因此,在空参数的情况下引发IllegalArgumentException会使API用户在程序尝试以编程方式更正问题时更难区分问题,例如通过向调用repeat中输入默认值!

(3)映射实际上会造成bug屏蔽的危险:为了将参数约束冲突映射到IllegalArgumentException,需要在每个具有任何约束参数的方法中编写一个外部try catch。但是,在这个catch块中简单地捕获runtimeexception是不可能的,因为这样做的风险是将您的自由方法抛出的文档化runtimeexception映射到illegalargumentexception中,即使它们不是由参数约束冲突引起的。因此,您需要非常具体,但即使这样做也不能保护您免受意外将另一个API的未记录运行时异常(即bug)映射到您的API的IllegalArgumentException的情况。因此,即使是最仔细的映射也有可能掩盖其他库制造商的编程错误,因为参数约束违反了方法的用户,这只是一种不体面的行为!

另一方面,在标准实践中,规则保持简单,异常原因保持无遮拦和特定。对于方法调用方,规则也很简单:-如果由于传递了非法值而遇到任何类型的文档化运行时异常,请使用默认值重复调用(对于此特定异常是必需的),或者更正代码。-另一方面,如果您包含了一个运行时异常,而该异常没有为给定的参数集记录下来,那么请将一个错误报告提交给方法的创建者,以确保他们的代码或他们的文档被修复。


如果使用IllegalArgumentException(字符串消息)来声明参数无效并尽可能给出详细信息,则接受的做法是…因此,要说发现一个参数为空,而异常为非空,您可以这样做:

1
2
if( variable == null )
    throw new IllegalArgumentException("The object 'variable' cannot be null");

实际上,您没有理由隐式使用"NullPointerException"。NulLoPoExtExchange是Java虚拟机在试图在空引用上执行代码时抛出的一个异常(如ToStTrand())。


抛出一个null参数(无论是NullPointerException还是自定义类型)所独有的异常会使自动化null测试更加可靠。这种自动化的测试可以通过反射和一组默认值来完成,就像在Guava的NullPointerTester中那样。例如,NullPointerTester会尝试调用以下方法…

1
2
3
4
5
6
Foo(String string, List<?> list) {
  checkArgument(string.length() > 0);
  // missing null check for list!
  this.string = string;
  this.list = list;
}

…有两个论点清单:"", nullnull, ImmutableList.of()。它将测试每个调用是否抛出预期的NullPointerException。为此,传递null列表不会生成NullPointerException。然而,它确实产生了一个IllegalArgumentException,因为NullPointerTester碰巧使用了默认的""字符串。如果NullPointerTester只期望NullPointerExceptionnull值,它就会捕获这个错误。如果它期望IllegalArgumentException,它就会错过它。


作为一个主观问题,这应该是封闭的,但由于它仍然是开放的:

这是我以前工作的地方采用的内部政策的一部分,而且效果非常好。这都是记忆造成的,所以我记不清确切的措辞。值得注意的是,他们没有使用检查异常,但这超出了问题的范围。他们使用的未经检查的异常分为三大类。

NullPointerException:不要有意抛出。当取消对空引用的引用时,仅由VM引发NPE。应尽一切可能确保不会抛出这些文件。@nullable和@notnull应与代码分析工具一起使用以查找这些错误。

IllegalArgumentException:当函数的参数不符合公共文档时引发,这样就可以根据传入的参数识别和描述错误。手术室的情况属于这一类。

IllegalstateException:调用函数时引发,该函数的参数在传递时是意外的,或者与方法所属对象的状态不兼容。

例如,有两个内部版本的indexoutofboundsException用于具有长度的对象。一个是IllegalstateException的子类,在索引大于长度时使用。IllegalArgumentException的另一个子类,在索引为负时使用。这是因为您可以向对象添加更多的项,并且参数将有效,而负数则永远无效。

正如我所说,这个系统工作得非常好,需要有人来解释为什么会有这样的区别:"根据错误的类型,您可以很容易地找到要做的事情。即使您实际上无法找出出错的地方,也可以找出在哪里捕获错误并创建其他调试信息。"

NullPointerException:处理空的大小写或放入断言,这样就不会引发NPE。如果您输入的断言只是其他两种类型中的一种。如果可能的话,继续调试,就好像断言是在那里一样。

IllegalArgumentException:您的呼叫站点出错。如果要传递的值来自另一个函数,请找出收到错误值的原因。如果传递的是某个参数,则会传播错误,检查调用堆栈,直到找到不返回预期值的函数为止。

IllegalstateException:您没有按正确的顺序调用函数。如果您正在使用某个参数,请检查它们并抛出描述该问题的IllegalArgumentException。然后,你可以将脸颊向上延伸,直到你找到问题所在。

不管怎样,他的观点是你只能把非法的断言复制到堆栈上。无法将IllegalstateExceptions或NullPointerExceptions向上传播,因为它们与您的函数有关。


一般来说,开发人员不应该抛出NullPointerException。当代码试图取消引用值为空的变量时,运行时会引发此异常。因此,如果您的方法想要显式地不允许空值,而不是刚好让空值引发NullPointerException,则应该抛出IllegalArgumentException。


二分法…它们不重叠吗?只有不重叠的部分才能形成二分法。正如我看到的:

1
throw new IllegalArgumentException(new NullPointerException(NULL_ARGUMENT_IN_METHOD_BAD_BOY_BAD));


一些托收假设使用NullPointerException而不是IllegalArgumentException拒绝null。例如,如果将包含null的集合与拒绝null的集合进行比较,第一个集合将在另一个集合上调用containsAll并捕获其NullPointerException而不是IllegalArgumentException。(我正在研究AbstractSet.equals的实现。)

您可以合理地认为,以这种方式使用未经检查的异常是一种反模式,将包含null的集合与不包含null的集合进行比较可能是一个真正应该产生异常的错误,或者将null放在集合中是一个坏主意。然而,除非你愿意说equals在这种情况下应该抛出一个例外,否则你必须记住,在某些情况下,NullPointerException是必需的,而在其他情况下则不是。(NPE之前的IAE,C之后的除外…)


尝试访问当前值为空的引用变量的对象时引发NullPointerException

当方法接收的参数格式与方法预期的不同时引发IllegalArgumentException


我想从其他非法参数中找出空参数,所以我从IAE派生了一个名为nullArgumentException的异常。甚至不需要读取异常消息,我知道一个空参数被传递到了一个方法中,通过读取消息,我发现了哪个参数是空的。我仍然使用IAE处理程序捕获nullArgumentException,但是在我的日志中,我可以很快看到不同之处。


根据您的场景,IllegalArgumentException是最佳选择,因为null不是您的财产的有效值。


理想情况下,不应引发运行时异常。应为您的方案创建选中的异常(业务异常)。因为如果这些异常中的任何一个被抛出并被记录,它会在浏览日志时误导开发人员。相反,业务异常不会造成这种恐慌,并且通常在对日志进行故障排除时被忽略。


从链接到上述两个例外的定义是IllegalArgumentException:引发以指示方法已传递非法或不适当的参数。NullPointerException:在需要对象的情况下,当应用程序尝试使用空值时引发。

这里最大的区别是,在检查方法的参数是否有效时,应该使用IllegalArgumentException。NullPointerException应该在对象为空时"使用"时使用。

我希望这有助于把两者放在一个角度上。


如果它是一个"setter",或者在我要让一个成员稍后使用的地方,我倾向于使用IllegalArgumentException。

如果这是我现在要在方法中使用的(取消引用),我会主动抛出一个nullpointerException。我更喜欢这个,而不是让运行时做它,因为我可以提供一个有用的消息(看起来运行时也可以这样做,但这是对另一天的咆哮)。

如果我要重写一个方法,我将使用被重写的方法使用的任何内容。


在这种情况下,IllegalArgumentException使用您的API向用户传递"不应为空"的明确信息。正如其他论坛用户指出的那样,只要你想用你的API把正确的信息传达给用户,你就可以使用NPE。

GaryF和TWIDT删除了推荐使用NPE的"有效Java"(我发誓)。查看如何构建其他好的API是了解如何构建API的最佳方法。

另一个很好的例子是查看SpringAPI。例如,org.springframework.beans.beanutils.instantiaclass(constructor ctor,object[]args)有一个assert.not null(ctor,"constructor must not be null")行。org.springframework.util.assert.notnull(object object,string message)方法检查传入的参数(object)是否为空,如果为空,则抛出一个新的IllegalArgumentException(message),然后在org.springframework.beans.beanutils.instantialClass(…)方法中捕获。


您应该抛出一个IllegalArgumentException,因为它将使程序员明显地看到他做了一些无效的事情。开发人员已经习惯于看到虚拟机抛出的NPE,以至于任何程序员都不会立即意识到自己的错误,并且会开始随机地四处查看,或者更糟的是,将代码归咎于"错误"。


如果您选择抛出一个NPE,并且在方法中使用了参数,那么显式检查空值可能是多余的,而且代价高昂。我想虚拟机已经为你做到了。