我有一个简单的属性设置方法,null
不适合这个特定的属性。我一直在这种情况下被撕裂:我应该扔一个IllegalArgumentException
,还是一个NullPointerException
?从 javadocs 来看,两者似乎都是合适的。有某种可以理解的标准吗?或者这只是你应该做任何你喜欢做的事情之一,而且两者都是正确的?
26 回答
您应该使用IllegalArgumentException
(IAE),而不是NullPointerException
(NPE),原因如下:
首先,NPE JavaDoc明确列出了适合 NPE 的情况。请注意,当使用不当时,它们都会被运行时抛出。null
相比之下,IAE JavaDoc再清楚不过了:“抛出来表明一个方法被传递了一个非法或不适当的参数。” 对,就是你!
其次,当您在堆栈跟踪中看到 NPE 时,您会假设什么?可能有人取消引用了null
. 当您看到 IAE 时,您假定堆栈顶部的方法调用者传入了一个非法值。同样,后一个假设是正确的,前一个是误导性的。
第三,既然IAE显然是为验证参数而设计的,你不得不假设它是默认的异常选择,那你为什么要选择NPE呢?当然不是因为不同的行为——你真的希望调用代码从 IAE 中分别捕获 NPE 并因此做一些不同的事情吗?您是否尝试传达更具体的错误消息?但是无论如何,您都可以在异常消息文本中执行此操作,就像所有其他不正确的参数一样。
第四,所有其他不正确的参数数据都会是IAE,那为什么不一致呢?为什么非法null
的如此特殊,以至于它应该与所有其他类型的非法论点分开?
最后,我接受其他答案给出的论点,即部分 Java API 以这种方式使用 NPE。但是,Java API 与从异常类型到命名约定的所有内容都不一致,所以我认为只是盲目地复制(您最喜欢的部分)Java API 并不足以胜过其他考虑。
IllegalArgumentException
如果您不想null
成为允许的值,似乎需要 an ,NullPointerException
如果您尝试使用结果为 的变量,则会抛出null
。
标准是扔NullPointerException
。一般不会出错的“Effective Java”在第 42 条(第一版)、第 60 条(第二版)或第 72 条(第三版)“支持使用标准异常”中简要讨论了这一点:
“可以说,所有错误的方法调用都归结为非法参数或非法状态,但其他异常通常用于某些类型的非法参数和状态。如果调用者在某些参数中传递 null 值,则禁止使用 null 值,约定规定抛出 NullPointerException 而不是 IllegalArgumentException。”
IllegalArgumentException
直到今天,当我注意到java.util.Objects.requireNonNull
Java 7 中的方法时,我都赞成抛出空参数。使用该方法,而不是这样做:
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
带有友好错误消息的 a),而只依赖于NullPointerException
当您在 null 上调用方法时会自动获得范围。如果您快速调用一个方法(即快速失败),那么您将获得基本相同的效果,只是对开发人员来说不太友好。大多数时候,最好明确检查并抛出有用的消息来指示哪个参数为空,但是如果性能要求不破坏方法/构造函数的已发布合同,则可以选择更改它。
我倾向于遵循 JDK 库的设计,尤其是集合和并发(Joshua Bloch、Doug Lea,这些人知道如何设计可靠的 API)。无论如何,JDK 中的许多 API 都会主动抛出NullPointerException
.
例如,用于Map.containsKey
状态的 Javadoc:
@throws NullPointerException 如果键为空并且此映射不允许空键(可选)。
抛出你自己的 NPE 是完全有效的。约定是在异常消息中包含为 null 的参数名称。
模式是:
public void someMethod(Object mustNotBeNull) {
if (mustNotBeNull == null) {
throw new NullPointerException("mustNotBeNull must not be null");
}
}
无论您做什么,都不要设置错误的值并在以后其他代码尝试使用它时抛出异常。这使得调试成为一场噩梦。您应该始终遵循“快速失败”的原则。
投票赞成 Jason Cohen 的论点,因为它很好地呈现。让我一步一步地肢解它。;-)
NPE JavaDoc明确指出, “空对象的其他非法用途”。如果它仅限于运行时在不应该遇到 null 的情况下,那么所有这些情况都可以更简洁地定义。
如果您假设错误,但假设封装被正确应用,您真的不应该关心或注意 null 是否被不适当地取消引用,而不是方法是否检测到不适当的 null 并触发异常。
-
- 更具体的非法经营性质
- 错误地允许空值的逻辑往往与错误地允许非法值的逻辑大不相同。例如,如果我正在验证用户输入的数据,如果我得到不可接受的值,则该错误的根源在于应用程序的最终用户。如果我得到一个空值,那就是程序员错误。
- 无效值可能会导致堆栈溢出、内存不足错误、解析异常等。事实上,大多数错误通常在某些时候以无效值的形式出现在某些方法调用中。出于这个原因,我认为 IAE 实际上是RuntimeException下所有异常中最常见的。
实际上,其他无效参数可能会导致各种其他异常。UnknownHostException、FileNotFoundException、各种语法错误异常、IndexOutOfBoundsException、认证失败等
总的来说,我觉得 NPE 受到了很大的诟病,因为传统上一直与未能遵循快速失败原则的代码相关联。再加上 JDK 未能用消息字符串填充 NPE,确实造成了一种没有充分根据的强烈负面情绪。实际上,从运行时的角度来看,NPE 和 IAE 之间的区别严格来说就是名称。从这个角度来看,你的名字越准确,你给来电者的清晰度就越高。
这是一个“圣战”风格的问题。换句话说,这两种选择都很好,但人们会有自己的偏好,他们会誓死捍卫。
如果它是一个setter
方法并且null
被传递给它,我认为抛出一个IllegalArgumentException
. 在NullPointerException
您尝试实际使用null
.
所以,如果你正在使用它并且它是null
, NullPointer
. 如果它被传入并且它是null
, IllegalArgument
.
Apache Commons Lang 有一个NullArgumentException,它执行了这里讨论的许多事情:它扩展了 IllegalArgumentException 并且它的唯一构造函数采用了应该是非空的参数的名称。
虽然我觉得抛出 NullArgumentException 或 IllegalArgumentException 之类的东西更准确地描述了特殊情况,但我和我的同事选择听从 Bloch 关于这个主题的建议。
不能更同意所说的话。早失败,快失败。非常好的例外咒语。
关于抛出哪个异常的问题主要是个人喜好问题。在我看来,IllegalArgumentException 似乎比使用 NPE 更具体,因为它告诉我问题出在我传递给方法的参数上,而不是在执行方法时可能生成的值。
我的 2 美分
实际上,抛出 IllegalArgumentException 或 NullPointerException 的问题在我看来只是对 Java 中的异常处理理解不完整的少数人的“圣战”。一般来说,规则很简单,如下所示:
- 必须尽快指出参数约束违规(-> 快速失败),以避免更难调试的非法状态
- 如果由于某种原因出现无效的空指针,则抛出 NullPointerException
- 如果出现非法数组/集合索引,则抛出 ArrayIndexOutOfBounds
- 如果数组/集合大小为负,则抛出 NegativeArraySizeException
- 如果存在上述未涵盖的非法参数,并且您没有其他更具体的异常类型,请将 IllegalArgumentException 作为废纸篓抛出
- 另一方面,如果由于某种正当原因而无法通过快速失败避免的 FIELD 内的约束违规,则将其捕获并重新抛出为 IllegalStateException 或更具体的检查异常。在这种情况下,切勿让原始 NullPointerException、ArrayIndexOutOfBounds 等传递!
将所有类型的参数约束违规映射到 IllegalArgumentException 的情况至少有三个很好的理由,第三个可能非常严重,以至于标志着实践不好的风格:
(1) 程序员不能安全地假设所有违反参数约束的情况都会导致 IllegalArgumentException,因为如果没有更具体的异常可用,大多数标准类都使用此异常而不是作为废纸篓。尝试将所有违反参数约束的情况映射到 API 中的 IllegalArgumentException 只会导致程序员在使用您的类时感到沮丧,因为标准库大多遵循违反您的规则的不同规则,并且您的大多数 API 用户也会使用它们!
(2) 映射异常实际上会导致另一种异常,由单继承引起:所有Java异常都是类,因此仅支持单继承。因此,没有办法创建一个真正说是 NullPointerException 和 IllegalArgumentException 的异常,因为子类只能从其中一个继承。因此,在出现空参数的情况下抛出 IllegalArgumentException 会使 API 用户在程序尝试以编程方式纠正问题时更难区分问题,例如通过将默认值输入到重复调用中!
(3) 映射实际上会产生错误屏蔽的危险:为了将参数约束违规映射到 IllegalArgumentException,您需要在每个具有任何约束参数的方法中编写外部 try-catch。但是,简单地在这个 catch 块中捕获 RuntimeException 是不可能的,因为这可能会将记录在案的 RuntimeExceptions 映射到 IllegalArgumentException 中,即使它们不是由违反参数约束引起的。因此,您需要非常具体,但即使这种努力也不能保护您免受意外将另一个 API 的未记录运行时异常(即错误)映射到 API 的 IllegalArgumentException 的情况。
另一方面,在标准实践中,规则保持简单,异常原因保持公开和具体。对于方法调用者,规则也很简单: - 如果您因为传递了非法值而遇到任何类型的记录运行时异常,请使用默认值重复调用(因为此特定异常是必要的),或者更正您的代码- 另一方面,如果您遇到运行时异常,但未记录给定参数集会发生,请向方法的制造者提交错误报告,以确保他们的代码或文档得到修复。
作为一个主观问题,这应该被关闭,但它仍然是开放的:
这是我以前工作的地方使用的内部政策的一部分,而且效果很好。这都是凭记忆,所以我不记得确切的措辞。值得注意的是,他们没有使用检查异常,但这超出了问题的范围。他们确实使用的未经检查的异常分为 3 个主要类别。
NullPointerException:不要故意抛出。NPE 在取消引用空引用时只能由 VM 抛出。将尽一切可能确保这些东西永远不会被抛出。@Nullable 和 @NotNull 应与代码分析工具结合使用以查找这些错误。
IllegalArgumentException:当函数的参数不符合公共文档时抛出,这样可以根据传入的参数来识别和描述错误。OP 的情况属于此类。
IllegalStateException:当调用函数并且其参数在传递时意外或与该方法所属的对象的状态不兼容时抛出。
例如,有两个内部版本的 IndexOutOfBoundsException 用于具有长度的事物。IllegalStateException 的一个子类,在索引大于长度时使用。IllegalArgumentException 的另一个子类,在索引为负数时使用。这是因为您可以向对象添加更多项目并且参数将是有效的,而负数永远不会有效。
正如我所说,这个系统运行得非常好,有人解释了为什么会有区别:“根据错误的类型,你很容易弄清楚该怎么做。即使你实际上无法弄清楚找出问题所在,您可以找出在哪里捕获该错误并创建额外的调试信息。”
NullPointerException:处理 Null 情况或放入断言,以便不抛出 NPE。如果您放入断言只是其他两种类型中的一种。如果可能,继续调试,就好像断言一开始就在那里一样。
IllegalArgumentException:您的呼叫站点有问题。如果传入的值来自另一个函数,请找出您收到错误值的原因。如果您传入其中一个参数,则会传播错误检查调用堆栈,直到找到未返回您期望的函数。
IllegalStateException:您没有以正确的顺序调用您的函数。如果您使用其中一个参数,请检查它们并抛出描述问题的 IllegalArgumentException。然后,您可以将脸颊向上传播到堆栈,直到找到问题为止。
无论如何,他的观点是您只能将 IllegalArgumentAssertions 复制到堆栈中。您无法将 IllegalStateExceptions 或 NullPointerExceptions 传播到堆栈上,因为它们与您的函数有关。
如果使用IllegalArgumentException( String message )来声明参数无效并提供尽可能多的详细信息,则公认的做法是……所以说发现参数为空而异常非空,你会做一些事情像这样:
if( variable == null )
throw new IllegalArgumentException("The object 'variable' cannot be null");
您几乎没有理由隐式使用“NullPointerException”。NullPointerException 是当您尝试在空引用上执行代码时 Java 虚拟机抛出的异常(如toString())。
抛出参数独有的异常null
(无论NullPointerException
是自定义类型)使自动化null
测试更加可靠。这种自动化测试可以通过反射和一组默认值来完成,就像在Guava的NullPointerTester
. 例如,NullPointerTester
将尝试调用以下方法...
Foo(String string, List<?> list) {
checkArgument(string.length() > 0);
// missing null check for list!
this.string = string;
this.list = list;
}
...带有两个参数列表:"", null
和null, ImmutableList.of()
. 它将测试这些调用中的每一个是否都会抛出预期的NullPointerException
. 对于此实现,传递null
列表不会产生NullPointerException
. 但是,它确实会产生一个,IllegalArgumentException
因为NullPointerTester
碰巧使用了默认字符串""
. 如果NullPointerTester
只期望值NullPointerException
,null
它会捕获错误。如果它期望IllegalArgumentException
,它会错过它。
一般来说,开发人员永远不应该抛出 NullPointerException。当代码尝试取消引用值为 null 的变量时,运行时会引发此异常。因此,如果您的方法想要明确禁止 null,而不是碰巧有一个 null 值引发 NullPointerException,则应该抛出 IllegalArgumentException。
一些集合假设使用而不是null
拒绝。例如,如果您将包含的集合与拒绝的集合进行比较,则第一个集合将调用另一个集合并捕获它的-- 但不是。(我正在研究 的实现。)NullPointerException
IllegalArgumentException
null
null
containsAll
NullPointerException
IllegalArgumentException
AbstractSet.equals
您可以合理地争辩说,以这种方式使用未经检查的异常是一种反模式,将包含null
的集合与不能包含的集合进行比较null
是一个可能确实应该产生异常的错误,或者null
根本就放入一个集合是一个坏主意. 然而,除非你愿意说equals
在这种情况下应该抛出异常,否则你会一直记住NullPointerException
在某些情况下是必需的,而在其他情况下则不需要。(“在 NPE 之前的 IAE,除了 'c' 之后......”)
NullPointerException
尝试使用当前值为 的引用变量访问对象时抛出null
。
IllegalArgumentException
当方法接收到格式与方法预期不同的参数时抛出。
我想从其他非法参数中挑选出 Null 参数,因此我从 IAE 派生了一个名为 NullArgumentException 的异常。甚至不需要阅读异常消息,我就知道一个空参数被传递到一个方法中,并且通过阅读消息,我找出哪个参数是空的。我仍然使用 IAE 处理程序捕获 NullArgumentException,但在我的日志中,我可以快速看到差异。
二分法...它们不重叠吗?只有整体中不重叠的部分才能形成二分法。照我看来:
throw new IllegalArgumentException(new NullPointerException(NULL_ARGUMENT_IN_METHOD_BAD_BOY_BAD));
根据您的情况,IllegalArgumentException
是最好的选择,因为null
它不是您的财产的有效价值。
上述两个异常的链接中的定义是 IllegalArgumentException: 抛出以指示方法已传递了非法或不适当的参数。NullPointerException:当应用程序在需要对象的情况下尝试使用 null 时引发。
这里最大的区别是在检查方法的参数是否有效时应该使用 IllegalArgumentException。NullPointerException 应该在当对象为空时被“使用”时使用。
我希望这有助于正确看待两者。
如果它是一个“setter”,或者我让一个成员稍后使用,我倾向于使用 IllegalArgumentException。
如果这是我现在要在方法中使用(取消引用)的东西,我会主动抛出 NullPointerException。我比让运行时更喜欢这样做,因为我可以提供有用的消息(似乎运行时也可以这样做,但这是另一天的咆哮)。
如果我要覆盖一个方法,我会使用被覆盖的方法使用的任何东西。
你应该抛出一个 IllegalArgumentException,因为它会让程序员明白他做了一些无效的事情。开发人员已经习惯了看到虚拟机抛出的 NPE,以至于任何程序员都不会立即意识到自己的错误,并且会开始随意四处寻找,或者更糟的是,责怪你的代码有“错误”。
在这种情况下,IllegalArgumentException 使用您的 API 向用户传达“不应为空”的明确信息。正如其他论坛用户指出的那样,只要您使用您的 API 向用户传达正确的信息,您就可以使用 NPE。
GaryF 和tweakt 删除了推荐使用NPE 的“Effective Java”(我发誓)参考。了解其他优秀 API 的构建方式是了解如何构建 API 的最佳方式。
另一个很好的例子是查看 Spring API。例如, org.springframework.beans.BeanUtils.instantiateClass(Constructor ctor, Object[] args) 有一个 Assert.notNull(ctor, "Constructor must not be null") 行。org.springframework.util.Assert.notNull(Object object, String message) 方法检查传入的参数(对象)是否为 null,如果是则抛出一个新的 IllegalArgumentException(message),然后在 org. springframework.beans.BeanUtils.instantiateClass(...) 方法。
理想情况下,不应抛出运行时异常。应为您的场景创建一个检查异常(业务异常)。因为如果抛出并记录这些异常中的任何一个,它就会在浏览日志时误导开发人员。相反,业务异常不会造成这种恐慌,并且在对日志进行故障排除时通常会被忽略。
如果您选择抛出 NPE 并且您在方法中使用该参数,则显式检查 null 可能是多余且昂贵的。我认为虚拟机已经为您做到了。