空参数应该抛出IllegalArgumentException还是NullPointerException?

574

我有一个简单的属性设置方法,对于这个特定的属性,null 是不合适的。在这种情况下,我一直很犹豫:是抛出 IllegalArgumentException,还是抛出 NullPointerException?从 javadocs 中来看,两者似乎都可以。是否有某种明确的标准?或者这只是其中一种情况,你应该按照自己的喜好去做,而且两种做法都是正确的呢?

26个回答

8
作为一个主观问题,这个问题应该被关闭,但是它仍然打开:
这是我以前的雇主使用的内部策略的一部分,并且效果非常好。这都是从记忆中得出的,所以我记不起确切的措辞。值得注意的是,他们没有使用已检查异常,但这超出了问题的范围。他们使用的未经检查的异常分为三个主要类别。
NullPointerException:不要有意抛出。只有在取消引用null引用时,VM才会抛出NPEs。必须尽一切可能确保永远不会抛出这些异常。@Nullable和@NotNull应与代码分析工具结合使用,以查找这些错误。
IllegalArgumentException:当函数的参数不符合公共文档的规定时抛出,这样可以根据传递的参数识别和描述错误。 OP的情况将属于此类别。
IllegalStateException:在调用函数并且其参数在传递时不受预期或与方法成员状态不兼容时抛出。
例如,在具有长度的事物中使用了两个IndexOutOfBoundsException的内部版本。一个是IllegalStateException的子类,如果索引大于长度则使用,另一个是IllegalArgumentException的子类,如果索引为负则使用。这是因为你可以将更多的项添加到对象中,该参数将有效,而负数永远无效。
正如我所说,这个系统非常好用,需要有人解释为什么存在这种区别:“根据错误类型,您可以很容易地找出要做什么。即使您实际上无法找出出了什么问题,您也可以找到捕获该错误并创建其他调试信息的位置。”
NullPointerException:处理Null情况或放置断言以避免抛出NPE。如果您放入一个断言,那么就是另外两种类型之一。如果可能,请继续调试,就好像在第一次就有断言一样。
IllegalArgumentException:您在调用站点上有些错误。如果传递的值来自另一个函数,请找出为什么会收到不正确的值。如果您传递了其中一个参数,请将错误检查传播到呼叫堆栈,直到找到未返回您期望的内容的函数。
IllegalStateException:您没有按正确顺序调用函数。如果您正在使用其中一个参数,请检查它们并抛出描述问题的IllegalArgumentException。然后,您可以将颊肉传播到堆栈上,直到找到问题所在。
无论如何,他的观点是,您只能将IllegalArgumentAssertions复制到堆栈上。您无法传播IllegalStateExceptions或NullPointerExceptions到堆栈上,因为它们与您的函数有关。

7

完全同意所说的内容。早失败、快失败,这是一个非常好的异常口号。

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

以上仅供参考。


6

抛出一个专门针对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;
}

这段内容涉及到it技术,测试了包含两个参数列表的方法:"", nullnull, ImmutableList.of()。测试结果应该会抛出预期的NullPointerException异常。对于此实现,传递一个null列表并不会产生NullPointerException异常。然而,它确实会产生IllegalArgumentException异常,因为NullPointerTester恰好使用默认字符串""。如果NullPointerTester只期望null值抛出NullPointerException异常,则可以捕获此错误。如果它期望IllegalArgumentException异常,则会漏掉此错误。


6

通常的做法是使用IllegalArgumentException( String message )来声明参数无效,并尽可能提供详细信息...所以如果要说发现了一个空参数而异常非空,您可以这样做:

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

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

请注意,避免使用空引用可以帮助您避免此类异常。因此,在编写代码时,请始终检查您的变量是否为空。


5

通常情况下,开发人员绝不应该抛出NullPointerException异常。当代码试图取消引用值为null的变量时,运行时会抛出此异常。因此,如果您的方法想要明确禁止使用null,而不是仅仅因为一个null值导致NullPointerException异常,那么您应该抛出IllegalArgumentException异常。


9
JavaDoc对NPE的看法有所不同:“应用程序应该抛出这个类的实例来指示空对象的其他非法使用。” 不要太绝对。 - Donz

5
一些集合假定使用NullPointerException而不是IllegalArgumentException拒绝null。例如,如果你比较一个包含null的集合和一个拒绝null的集合,第一个集合将在另一个集合上调用containsAll并捕获它的NullPointerException - 而不是IllegalArgumentException。(我正在查看AbstractSet.equals的实现。)
你可以合理地认为以这种方式使用未经检查的异常是一种反模式,比较包含null的集合和不能包含null的集合是一种可能的错误,真正应该产生异常,或者将null放入集合中本身就是一个坏主意。然而,除非你愿意说equals在这种情况下应该抛出异常,否则你必须记住,在某些情况下需要NullPointerException,而在其他情况下则不需要。(“在'c'之前使用IAE,之后使用NPE...”)
有些构建工具可能会自动插入空检查。值得注意的是,当将可能为空的值传递给Java API时,Kotlin编译器会自动进行此操作。当检查失败时,结果是NullPointerException。因此,为了给任何使用Kotlin和Java的用户提供一致的行为,您需要使用NullPointerException

我不明白为什么contains方法会根据集合中的元素抛出NPE异常。据我所知,唯一可能抛出NPE异常的原因是如果集合本身为null(在这种情况下,NPE异常被抛出是因为它试图访问其迭代器)。然而,这引发了一个问题,即是否应该检查空输入或者让其传播直到尝试访问它。 - Alowaniak
2
new TreeSet<>().containsAll(Arrays.asList((Object) null)); 抛出 NPE,因为 List 包含 null - Chris Povirk
1
啊,确实,TreeSet#contains会抛出NPE,“如果指定的元素为null并且此集合使用自然排序,或者其比较器不允许null元素”。我只看了允许null的AbstractSet,我的错。个人认为,如果是这种情况,它不仅应该返回false,而且根本不可能添加null。 - Alowaniak

4

当试图访问一个引用变量当前值为null的对象时,会抛出NullPointerException异常。

当方法接收到与其期望格式不同的参数时,会抛出IllegalArgumentException异常。


4
根据您的情况,IllegalArgumentException 是最合适的选择,因为null 不是您属性的有效值。

4

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

throw new IllegalArgumentException(new NullPointerException(NULL_ARGUMENT_IN_METHOD_BAD_BOY_BAD));

1
这将使异常创建的开销增加一倍,而且并没有真正帮助捕获“NullPointerException”,因为它什么也做不了。唯一有用的是“IllegalNullPointerArgumentException extends IllegalArgumentException, NullPointerException”,但我们没有多重继承。 - maaartinus
我认为更具体的异常应该被更一般的异常所包含。NPE是针对表达式的,而IAE是针对方法的。由于方法包含包含表达式的语句,因此IAE更为一般化。 - Sgene9
关于开销,我不清楚。但是由于堆栈跟踪基本相同,除了异常名称在中间更改之外,我认为双重异常的开销不应该太大。如果有人担心开销,可以使用“if”语句返回null或-1而不是抛出异常。 - Sgene9

4

我希望把空参数从其他非法参数中单独分离出来,所以我从IAE派生了一个异常命名为NullArgumentException。即使不需要阅读异常消息,我也知道一个空参数被传递到一个方法中,并通过读取消息找出哪个参数是空的。我仍然使用IAE处理程序捕获NullArgumentException,但在我的日志中,我可以快速看到区别。


我采用了"throw new IllegalArgumentException("foo == null")"的方法。无论如何你都需要记录变量名(以确保你正在查看正确的语句等)。 - Thorbjørn Ravn Andersen

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