null参数的IllegalArgumentException或NullPointerException?

时间:2008-08-06 19:26:31

标签: java exception null nullpointerexception illegalargumentexception

我有一个简单的属性setter方法,null不适合这个特定的属性。在这种情况下我总是被撕裂:我应该抛出IllegalArgumentException还是NullPointerException?从javadocs看,两者似乎都合适。有某种理解标准吗?或者这只是你应该做的任何事情之一,而且两者都是正确的?

26 个答案:

答案 0 :(得分:407)

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

首先,NPE JavaDoc明确列出了NPE合适的情况。请注意,当null使用不当时,所有这些都被运行时抛出。相比之下,IAE JavaDoc无法更明确:“抛出表示方法已被传递为非法或不恰当的参数。”是的,就是你!

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

第三,由于IAE明确地设计用于验证参数,您必须将其视为异常的默认选择,那么为什么要选择NPE呢?当然不是出于不同的行为 - 您是否真的希望调用代码将IAE与IAE分开捕获并做一些不同的结果?您是否尝试传达更具体的错误消息?但是你可以在异常消息文本中这样做,就像你应该对所有其他不正确的参数一样。

第四,所有其他不正确的参数数据都是IAE,为什么不一致?为什么非法的null如此特殊以至于它应该与所有其他类型的非法论证分开?

最后,我接受其他答案给出的论点,即Java API的某些部分以这种方式使用NPE。但是,Java API与从异常类型到命名约定的所有内容都不一致,因此我认为盲目地复制(您最喜欢的部分)Java API并不足以胜过其他考虑因素。

答案 1 :(得分:282)

如果您不希望IllegalArgumentException成为允许值,则会调用null,如果您尝试,则会引发NullPointerException使用一个结果为null的变量。

答案 2 :(得分:154)

标准是抛出NullPointerException。通常绝对可靠的“有效Java”在第42项(第一版),第60项(第二版)或第72项(第三版)“赞成使用标准例外”中简要讨论了这一点:

  

“可以说,所有错误的方法   调用归结为非法   争论或非法国家,但其他   例外是标准使用的   某些非法论点和   状态。如果调用者传入null   一些参数为null值   公约规定禁止   抛出NullPointerException   而不是IllegalArgumentException。“

答案 3 :(得分:131)

我一直赞成将IllegalArgumentException抛给null参数,直到今天,当我注意到Java 7中的java.util.Objects.requireNonNull方法时。使用该方法,而不是执行:

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

你可以这样做:

Objects.requireNonNull(param);

如果您传递的参数为NullPointerException,则会抛出null

鉴于该方法在java.util的中间是正确的,我认为它的存在是一个非常强烈的迹象,即抛出NullPointerException是“Java做事的方式”。

我想我无论如何都会决定。

请注意,关于硬调试的参数是假的,因为您当然可以向NullPointerException提供一条消息,说明什么是null以及为什么它不应该为null。与IllegalArgumentException一样。

NullPointerException的一个额外优势是,在高性能关键代码中,您可以省略对null的显式检查(以及带有友好错误消息的NullPointerException),并且只需依赖于调用null参数的方法时,您将自动获得NullPointerException。如果你快速调用一个方法(即快速失败),那么你的效果基本相同,对开发人员来说不是那么友好。大多数情况下,最好明确检查并抛出一条有用的消息来指示哪个参数为null,但如果性能指示不改变方法/构造函数的已发布合约,那么可以选择更改它。

答案 4 :(得分:67)

我倾向于遵循JDK库的设计,尤其是Collections and Concurrency(Joshua Bloch,Doug Lea,这些人知道如何设计可靠的API)。无论如何,JDK中的许多API主动抛出NullPointerException

例如,Map.containsKey的Javadoc声明:

  

@throws NullPointerException如果键为null且此映射     不允许空键(可选)。

投掷自己的NPE是完全有效的。惯例是在异常消息中包含参数名称null。

模式如下:

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

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

答案 5 :(得分:42)

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

  • NPE JavaDoc明确说明,“其他非法使用空对象”。如果它仅限于运行时遇到null的情况,那么所有这些情况都可以更加简洁地定义。

  • 如果你假设错误的话,无法帮助它,但假设封装正确应用,你真的不应该关心或注意null是否被不正确地解除引用而不是方法是否检测到不合适的null并且被解雇例外。

  • 由于多种原因,我选择NPE而不是IAE

    • 更具体的是非法行动的性质
    • 错误地允许空值的逻辑往往与错误地允许非法值的逻辑非常不同。例如,如果我正在验证用户输入的数据,如果我得到的值是不可接受的,那么该错误的来源就是应用程序的最终用户。如果我得到null,那就是程序员错误。
    • 无效的值可能导致堆栈溢出,内存不足错误,解析异常等问题。实际上,大多数错误通常在某些情况下作为某些方法调用中的无效值出现。出于这个原因,我认为IAE实际上是RuntimeException下所有异常的 MOST GENERAL
  • 实际上,其他无效参数可能导致各种其他异常。 UnknownHostExceptionFileNotFoundException,各种语法错误例外,IndexOutOfBoundsException,身份验证失败等等。

一般来说,我觉得NPE受到很多诽谤,因为传统上代码与fail fast principle无法遵循的代码相关联。而且,加上JDK未能用消息字符串填充NPE,确实产生了一种强烈的负面情绪,而这种情绪并不是很有根据。实际上,从运行时的角度来看,NPE和IAE之间的区别仅仅是名称。从这个角度来看,你对名字越精确,你给呼叫者的清晰度就越高。

答案 6 :(得分:20)

这是一个“圣战”式的问题。换句话说,两种选择都是好的,但人们会有自己的偏好,他们会为死亡辩护。

答案 7 :(得分:17)

如果它是setter方法并且null正在传递给它,我认为抛出IllegalArgumentException会更有意义。在您尝试实际使用NullPointerException时,null似乎更有意义。

所以,如果你正在使用它,它是nullNullPointer。如果它被传入并且是nullIllegalArgument

答案 8 :(得分:10)

Apache Commons Lang有一个NullArgumentException来完成这里讨论的一些事情:它扩展了IllegalArgumentException,它唯一的构造函数接受了应该是非null的参数的名称。

虽然我觉得抛出像NullArgumentException或IllegalArgumentException这样的东西更准确地描述了特殊情况,但我和我的同事们已经选择遵循Bloch关于这个主题的建议。

答案 9 :(得分:7)

不能同意所说的内容。早早失败,快失败。相当不错的例外口头禅。

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

My 2 Cents

答案 10 :(得分:6)

接受的做法是使用 IllegalArgumentException(String message)来声明参数无效并尽可能详细地说明......所以说发现参数为null异常非null,你会做这样的事情:

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

您几乎没有理由隐式使用“NullPointerException”。当您尝试在空引用上执行代码时(例如 toString()),NullPointerException是Java虚拟机抛出的异常。

答案 11 :(得分:6)

实际上,抛出IllegalArgumentException或NullPointerException的问题在我看来只是对少数人的“圣战”,他们对Java中的异常处理有一个简单的理解。一般来说,规则很简单,如下:

  • 参数约束违规必须尽快指示( - >快速失败),以避免难以调试的非法状态
  • 如果出于任何原因无效的空指针,则抛出NullPointerException
  • 如果是非法数组/集合索引,则抛出ArrayIndexOutOfBounds
  • 如果数组/集合大小为负,请抛出NegativeArraySizeException
  • 如果非法参数未被上述内容覆盖,并且您没有其他更具体的异常类型,则将IllegalArgumentException作为废纸篓抛出
  • 另一方面,如果由于某些有效原因快速失败而无法避免的约束违反WITH LIN的情况下,捕获并重新抛出为IllegalStateException或更具体的已检查异常。在这种情况下永远不要传递原始的NullPointerException,ArrayIndexOutOfBounds等!

将所有类型的参数约束违规映射到IllegalArgumentException的情况至少有三个非常好的理由,第三个可能是如此严重以至于标记练习不良风格:

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

(2)映射异常实际上会导致由单一继承引起的不同类型的异常:所有Java异常都是类,因此仅支持单继承。因此,没有办法创建一个真正说NullPointerException和IllegalArgumentException的异常,因为子类只能从一个或另一个继承。如果出现null参数,则抛出IllegalArgumentException会使API用户更难以在程序尝试以编程方式纠正问题时区分问题,例如通过将默认值提供给调用重复!

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

另一方面,通过标准练习,规则保持简单,异常原因保持不受掩盖和具体。对于方法调用者,规则也很简单: - 如果由于传递了非法值而遇到任何类型的文档运行时异常,请使用默认值重复调用(必须执行此特定异常),或更正代码 - 另一方面,如果您遇到一个未记录的运行时异常,对于给定的一组参数,请向该方法的制造商提交错误报告,以确保其代码或文档是固定的。

答案 12 :(得分:5)

抛出null参数独有的异常(无论NullPointerException还是自定义类型),使自动null测试更加可靠。这种自动化测试可以使用反射和一组默认值完成,如GuavaNullPointerTester。例如,NullPointerTester将尝试调用以下方法...

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仅需要NullPointerException null值,则会捕获该错误。如果它期望IllegalArgumentException,则会错过它。

答案 13 :(得分:5)

作为一个主观问题,这应该被关闭,但因为它仍然是开放的:

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

NullPointerException:不要故意扔掉。取消引用空引用时,仅由VM抛出NPE。所有可能的努力都是为了确保永远不会抛出这些。 @Nullable和@NotNull应与代码分析工具结合使用,以发现这些错误。

IllegalArgumentException:当函数的参数不符合公共文档时抛出,这样可以根据传入的参数识别和描述错误.OP的情况属于这一类。

IllegalStateException:在调用函数时抛出,并且它的参数在传递时是意外的,或者与方法所属的对象的状态不兼容。

例如,有两个内部版本的IndexOutOfBoundsException用于具有长度的东西。一个IllegalStateException的子类,如果索引大于长度,则使用它。另一个是IllegalArgumentException的子类,如果索引为负数则使用。这是因为您可以向对象添加更多项目,参数将有效,而负数永远无效。

正如我所说,这个系统工作得非常好,并且需要有人解释为什么存在这样的区别:“根据错误的类型,你可以直截了当地弄清楚要做什么。即使你能做到'实际上找出问题所在,你可以找出在哪里捕获该错误并创建其他调试信息。“

NullPointerException:处理Null案例或放入断言以便不抛出NPE。如果你输入断言只是其他两种类型中的一种。如果可能的话,继续调试,好像首先是断言一样。

IllegalArgumentException:您的呼叫站点出了问题。如果传入的值来自另一个函数,请找出收到错误值的原因。如果你传入一个参数传播,那么错误会检查调用堆栈,直到找到没有返回你期望的函数。

IllegalStateException:您没有按正确的顺序调用函数。如果您正在使用其中一个参数,请检查它们并抛出描述该问题的IllegalArgumentException。然后,您可以将面颊向上传播到堆栈,直到找到问题为止。

无论如何,他的观点是你只能将IllegalArgumentAssertions复制到堆栈中。你无法将IllegalStateExceptions或NullPointerExceptions传播到堆栈中,因为它们与你的函数有关。

答案 14 :(得分:5)

通常,开发人员应该永远不会抛出NullPointerException。当代码尝试取消引用值为null的变量时,运行时抛出此异常。因此,如果你的方法想要显式地禁止null,而不是只是发生一个空值引发NullPointerException,你应该抛出一个IllegalArgumentException。

答案 15 :(得分:4)

尝试使用当前值为null的引用变量访问对象时抛出NullPointerException

当方法接收的格式与方法预期不同时抛出IllegalArgumentException

答案 16 :(得分:4)

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

您可以合理地争辩说,以这种方式使用未经检查的异常是反模式,将包含null的集合与不能包含null的集合进行比较可能是一个真正应该出错的错误产生异常,或者将null放入集合中是一个坏主意。尽管如此,除非你愿意说equals应该在这种情况下抛出异常,否则你会忘记在某些情况下需要NullPointerException而在其他情况下不需要{{1}}。 (“NPE之前的IAE除了'c'之后......”)

答案 17 :(得分:4)

二分法......它们是否不重叠?只有整体的非重叠部分才能成为二分法。我认为:

throw new IllegalArgumentException(new NullPointerException(NULL_ARGUMENT_IN_METHOD_BAD_BOY_BAD));

答案 18 :(得分:4)

我想从其他非法参数中挑出Null参数,因此我从IAE派生了一个名为NullArgumentException的异常。甚至不需要读取异常消息,我知道将null参数传递给方法并通过读取消息,我找出哪个参数为null。我仍然使用IAE处理程序捕获NullArgumentException,但在我的日志中我可以快速看到差异。

答案 19 :(得分:3)

根据您的情况,IllegalArgumentException是最佳选择,因为null不是您商家的有效值。

答案 20 :(得分:0)

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

答案 21 :(得分:-1)

你应该抛出一个IllegalArgumentException,因为它会让程序员明白他做了一些无效的事情。开发人员习惯于看到VM抛出的NPE,任何程序员都不会立即意识到他的错误,并且会开始随意查看,或者更糟糕的是,将代码归咎于“错误”。

答案 22 :(得分:-1)

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

GaryF和tweakt删除了“Effective Java”(我发誓)推荐使用NPE的参考资料。看看如何构建其他优秀的API是了解如何构建API的最佳方式。

另一个很好的例子是查看Spring API。例如,org.springframework.beans.BeanUtils.instantiateClass(Constructor ctor,Object [] args)有一个Assert.notNull(ctor,“Constructor must not null”)行。 org.springframework.util.Assert.notNull(Object object,String message)方法检查传入的参数(对象)是否为null,如果是,则抛出一个新的IllegalArgumentException(消息),然后在组织中捕获。 springframework.beans.BeanUtils.instantiateClass(...)方法。

答案 23 :(得分:-1)

如果它是“setter”,或者某个地方我正在使用以后使用的成员,我倾向于使用IllegalArgumentException。

如果我现在要在方法中使用(取消引用),我会主动抛出NullPointerException。我比运行时更喜欢这个,因为我可以提供一个有用的消息(似乎运行时也可以这样做,但这是另一天的咆哮)。

如果我覆盖了一个方法,我会使用被覆盖的方法使用的任何方法。

答案 24 :(得分:-1)

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

这里的最大区别是在检查方法的参数是否有效时应该使用IllegalArgumentException。当对象为空时,只要对象被“使用”,就应该使用NullPointerException。

我希望这有助于将两者放在一起。

答案 25 :(得分:-5)

如果您选择抛出NPE并且在方法中使用该参数,则显式检查null可能是多余且昂贵的。我认为VM已经为你做了这件事。