我有一个简单的属性设置方法 null 不适合此特定属性。我一直在这样的情况下纠结:我应该扔一个 IllegalArgumentException, ,或一个 NullPointerException?从 javadoc 来看,两者似乎都合适。有某种可以理解的标准吗?或者这只是你应该做任何你喜欢做的事情之一,而且两者都是正确的?

有帮助吗?

解决方案

这似乎是一个 IllegalArgumentException 如果你不想要的话就会被要求 null 是一个允许的值,并且 NullPointerException 如果你试图这样做,就会被抛出 使用 一个变量,结果是 null.

其他提示

你应该使用 IllegalArgumentException (国际原子能机构),不是 NullPointerException (NPE)的原因如下:

首先, NPE Java文档 明确列出 NPE 合适的情况。请注意,所有这些都被抛出 按运行时 什么时候 null 使用不当。相比之下, IAE Java文档 再清楚不过了:“被抛弃表明一种方法已经通过了非法或不适当的论点。”是的,就是你!

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

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

第四,所有其他不正确的参数数据都会是IAE,那为什么不一致呢?为什么说是违法的 null 是否如此特殊以至于值得将其与所有其他类型的非法参数分开?

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

标准是扔 NullPointerException. 。一般无误的“Effective Java”在第 42 条(第一版)、第 60 条(第二版)或第 72 条(第三版)“赞成使用标准异常”中对此进行了简要讨论:

“可以说,所有错误的方法调用都归结为非法论点或非法状态,但其他例外是​​用于某些非法论点和国家的标准。如果呼叫者在禁止的一些参数中通过了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 带有友好的错误消息),并且只需依赖 NullPointerException 当您对 null 参数调用方法时,您会自动获得。假设您快速调用一个方法(即快速失败),那么你基本上会得到相同的效果,只是对开发人员来说不太友好。大多数时候,最好显式检查并抛出一条有用的消息来指示哪个参数为空,但如果性能要求,可以选择更改该参数而不破坏方法/构造函数的已发布约定,这也是很好的选择。

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

例如,Javadoc Map.containsKey 状态:

@throws nullpoInterException如果键为空,并且此地图不允许null键(可选)。

抛出你自己的 NPE 是完全正确的。约定是在异常消息中包含为 null 的参数名称。

模式如下:

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

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

对杰森·科恩的论点投了赞成票,因为它表达得很好。让我一步步拆解吧。;-)

  • NPE Java文档 明确表示, “空对象的其他非法使用”. 。如果它仅限于运行时在不应该遇到空值时遇到空值的情况,那么所有此类情况都可以定义得更加简洁。

  • 如果你假设错误的事情,那也无济于事,但假设封装应用得当,你真的不应该关心或注意空值是否被不恰当地取消引用。方法是否检测到不适当的 null 并引发异常。

  • 我会选择 NPE 超过 国际航空工程师协会 由于多种原因

    • 更具体地说明了违法经营的性质
    • 错误地允许空值的逻辑往往与错误地允许非法值的逻辑有很大不同。例如,如果我正在验证用户输入的数据,如果我得到不可接受的值,则该错误的根源在于应用程序的最终用户。如果我得到一个空值,那就是程序员错误。
    • 无效值可能会导致堆栈溢出、内存不足错误、解析异常等。事实上,大多数错误通常在某些时候表现为某些方法调用中的无效值。出于这个原因,我认为 IAE 实际上是 最一般 RuntimeException 下的所有异常。
  • 实际上,其他无效参数可能会导致各种其他异常。 未知主机异常, 文件未找到异常, 、各种语法错误异常、 索引越界异常, 、身份验证失败等等。

总的来说,我觉得 NPE 受到了很多诟病,因为传统上它与不遵循规范的代码相关联。 快速失败原则. 。再加上 JDK 未能用消息字符串填充 NPE,确实造成了一种没有充分根据的强烈负面情绪。事实上,从运行时的角度来看,NPE 和 IAE 之间的区别严格在于名称。从这个角度来看,你的名字越准确,你给呼叫者的信息就越清晰。

这是一个“圣战”风格的问题。换句话说,两种选择都不错,但人们会有自己的偏好,并且会誓死捍卫。

如果它是一个 setter 方法和 null 正在传递给它,我认为抛出一个更有意义 IllegalArgumentException. 。A NullPointerException 在您尝试实际使用的情况下似乎更有意义 null.

所以,如果你正在使用它并且它是 null, NullPointer. 。如果它被传入并且它是 null, IllegalArgument.

Apache Commons Lang 有一个 空参数异常 它做了一些这里讨论的事情:它扩展了 IllegalArgumentException ,并且其唯一的构造函数采用本应为非空的参数名称。

虽然我认为抛出 NullArgumentException 或 IllegalArgumentException 之类的内容可以更准确地描述异常情况,但我和我的同事选择遵循 Bloch 关于该主题的建议。

非常同意所说的内容。早失败,快失败。非常好的例外口头禅。

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

我的2美分

可接受的做法如果使用 IllegalArgumentException(字符串消息) 声明参数无效并提供尽可能多的详细信息...因此,如果发现参数为空而异常非空,则可以执行以下操作:

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

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

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

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

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

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

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

(3) 映射实际上会产生错误屏蔽的危险:为了将参数约束违规映射到 IllegalArgumentException,您需要在每个具有任何约束参数的方法中编写外部 try-catch。然而,简单地在此 catch 块中捕获 RuntimeException 是不可能的,因为这存在将您中使用的自由方法抛出的已记录的 RuntimeException 映射到 IllegalArgumentException 的风险,即使它们不是由参数约束违规引起的。因此,您需要非常具体,但即使这样的努力也无法保护您免受意外映射另一个 API 的未记录的运行时异常的情况(即错误)到 API 的 IllegalArgumentException 中。因此,即使是最仔细的映射也有可能将其他库制造商的编程错误掩盖为方法用户的参数约束违规,这简直是可笑的行为!

另一方面,在标准实践中,规则保持简单,异常原因保持公开且具体。对于方法调用者来说,规则也很简单:- 如果您遇到了任何类型的记录的运行时例外,因为您通过了非法值,请用默认值重复调用(对于此特定例外是必要的),或更正您的代码 - 如果另一方面,您可以启用运行时例外情况。没有记录在给定的一组参数的情况下,请将错误报告提交方法的制造商,以确保其代码或文档已固定。

一般来说,开发者应该 绝不 抛出空指针异常。当代码尝试取消引用值为 null 的变量时,运行时会引发此异常。因此,如果您的方法想要显式禁止 null,而不是恰好有 null 值引发 NullPointerException,则应该抛出 IllegalArgumentException。

抛出一个独有的异常 null 参数(是否 NullPointerException 或自定义类型)使自动化 null 测试更可靠。这种自动化测试可以通过反射和一组默认值来完成,如下所示 番石榴NullPointerTester. 。例如, 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, ,它错过了。

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

这是我以前工作的地方使用的内部政策的一部分,效果非常好。这全是凭记忆,所以我记不清具体的措辞了。值得注意的是,他们没有使用检查异常,但这超出了问题的范围。他们使用的未经检查的异常分为 3 个主要类别。

空指针异常:请勿故意投掷。NPE 仅在取消引用空引用时由 VM 抛出。应尽一切努力确保这些永远不会被抛出。@Nullable 和 @NotNull 应与代码分析工具结合使用来查找这些错误。

非法参数异常:当函数的参数不符合公共文档时抛出,以便可以根据传入的参数来识别和描述错误。OP 的情况就属于这一类。

非法状态异常:当调用函数并且其参数在传递时是意外的或与该方法所属的对象的状态不兼容时抛出。

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

正如我所说,这个系统运行得非常好,需要有人来解释为什么会有这样的区别:“根据错误的类型,您可以很简单地找出该怎么做。即使您实际上无法弄清楚出了什么问题,您也可以找出在哪里捕获该错误并创建额外的调试信息。”

空指针异常:处理 Null 情况或放入断言,以便不会引发 NPE。如果你放入断言只是其他两种类型之一。如果可能的话,继续调试,就好像断言一开始就存在一样。

非法参数异常:您的呼叫站点有问题。如果传入的值来自另一个函数,请找出您收到错误值的原因。如果您传入参数之一,则传播错误会检查调用堆栈,直到找到未返回预期结果的函数。

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

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

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

二分法...它们不重叠吗?只有整体中不重叠的部分才能形成二分法。照我看来:

throw new IllegalArgumentException(new NullPointerException(NULL_ARGUMENT_IN_METHOD_BAD_BOY_BAD));

一些集合假设 null 被拒绝使用 NullPointerException 而不是 IllegalArgumentException. 。例如,如果您比较包含以下内容的集合: null 到一个拒绝的集合 null, ,第一组将调用 containsAll 另一边并抓住它 NullPointerException - 但不是 IllegalArgumentException. 。(我正在研究实施 AbstractSet.equals.)

您可以合理地争辩说,以这种方式使用未经检查的异常是一种反模式,即比较包含以下内容的集合: null 到不能包含的集合 null 确实是一个可能的错误 应该 产生一个异常,或者把 null 放在一个集合中根本不是一个好主意。尽管如此,除非你愿意这么说 equals 在这种情况下应该抛出异常,你必须记住这一点 NullPointerException 在某些情况下需要,但在其他情况下则不需要。(“IAE 在 NPE 之前,除了‘c’之后......”)

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

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

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

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

上面两个例外的链接的定义是非法分解:抛出该异常表示向方法传递了非法或不适当的参数。空指针异常:当应用程序在需要对象的情况下尝试使用 null 时抛出。

这里最大的区别是在检查方法的参数是否有效时应该使用 IllegalArgumentException。每当对象为 null 时被“使用”时,就应该使用 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 可能是多余且昂贵的。我认为虚拟机已经为你做到了这一点。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top