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