null参数的IllegalArgumentException或NullPointerException ?

我有一个简单的setter方法的属性和null不适合这个特定的属性。在这种情况下我总是很纠结:我应该扔IllegalArgumentException还是NullPointerException?从javadocs来看,两者都很合适。是否存在某种公认的标准?或者这只是其中一件事,你应该做任何你喜欢做的事情,两种都是正确的?

189256 次浏览

如果您不希望null是一个允许的值,则似乎会调用IllegalArgumentException,如果您试图使用是一个结果为null的变量,则会抛出NullPointerException

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

如果你正在使用它,它是nullNullPointer。如果传入的是nullIllegalArgument

我倾向于遵循JDK库的设计,特别是集合和并发(Joshua Bloch, Doug Lea,这些人知道如何设计可靠的api)。不管怎样,JDK中的许多api都会主动抛出NullPointerException

例如,Map.containsKey的Javadoc声明:

@抛出NullPointerException如果键是空的,这个映射 不允许空键(可选)

举办自己的NPE是完全合理的。约定是在异常消息中包含为空的参数名。

模式是这样的:

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

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

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

公认的做法是使用(字符串消息)声明一个参数是无效的,并尽可能多地提供详细信息…所以说,一个参数被发现是空的,而异常是非空的,你会这样做:

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

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

标准是扔NullPointerException。通常不会出错的“有效Java”在第42项(第一版)、第60项(第二版)或第72项(第三版)中简要讨论了这一点。“赞成使用标准异常”:

"可以说,所有错误的方法 调用归结为非法 争论或非法国家,但其他 异常通常用于 一些不合法的论点 州。如果调用方传入null 某个参数,其值为空 是被禁止的吗 NullPointerException被抛出 而不是IllegalArgumentException。" < / p >

以上两个例外的链接的定义是 IllegalArgumentException:抛出该异常,表示方法被传递了一个非法或不适当的参数。 NullPointerException:当应用程序试图在需要对象的情况下使用null时抛出

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

我希望这能帮助你正确看待这两者。

我完全同意你说的话。早失败,快失败。非常好的异常咒语。

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

我的2美分

如果它是一个“setter”,或者我要获取一个成员稍后使用的地方,我倾向于使用IllegalArgumentException。

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

如果我重写一个方法,我使用被重写的方法使用的任何东西。

通常,开发人员应该从来没有抛出NullPointerException。当代码试图解引用值为null的变量时,运行时将引发此异常。因此,如果你的方法想要显式禁止null,而不是恰好有一个空值引发一个NullPointerException,你应该抛出一个IllegalArgumentException。

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

首先,JavaDoc神经性肺水肿显式列出了适合使用NPE的情况。注意,当不恰当地使用null时,它们都会抛出运行时。相比之下,IAE JavaDoc再清楚不过了:“抛出是为了表明一个方法被传递了一个非法或不适当的参数。”没错,就是你!

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

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

第四,其他所有不正确的参数数据都会被IAE,为什么不一致呢?为什么非法的null如此特殊,以至于它值得一个与所有其他类型的非法参数分开的例外?

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

您应该抛出一个IllegalArgumentException,因为这将使程序员清楚地知道他做了一些无效的事情。开发人员太习惯于看到VM抛出的NPE,以至于任何程序员都不会立即意识到自己的错误,并开始随机查看,或者更糟糕的是,指责你的代码“有bug”。

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

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

GaryF和tweak放弃了“有效Java”(我发誓)的参考,建议使用NPE。看看其他好的API是如何构造的,这是了解如何构造你的API的最好方法。

另一个很好的例子是查看Spring api。例如,org.springframework.beans.BeanUtils。instantiateClass(构造函数ctor, Object[] args)有一个Assert。notNull(ctor,“构造函数不能为空”)行。org.springframework.util.Assert。notNull(对象对象,字符串消息)方法检查传入的参数(对象)是否为空,如果为空,则抛出一个新的IllegalArgumentException(消息),然后在org.springframework.beans.BeanUtils.instantiateClass(…)方法中捕获。

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

Apache Commons Lang有一个NullArgumentException,它做了这里讨论的许多事情:它扩展了IllegalArgumentException,它唯一的构造函数采用了参数的名称,而参数的名称本应该是非空的。

虽然我觉得抛出NullArgumentException或IllegalArgumentException之类的异常更准确地描述了异常情况,但我和同事们还是选择遵从Bloch在这个问题上的建议。

给杰森·科恩的论点投了一票,因为它表现得很好。让我一步一步地分解它。: -)

  • NPE JavaDoc明确表示," null对象的其他非法使用"。如果只将它限制在运行时遇到不应该的空值的情况下,那么所有这些情况的定义都可以更简洁。

  • 如果假设错误,也没有办法,但假设封装应用正确,您真的不应该关心或注意是否不适当地解除了null引用,还是方法检测到不适当的null并触发异常。

  • 我选择肺水肿而不是管理学院有多种原因

    • 它对非法经营的性质更加具体
    • 错误地允许空值的逻辑往往与错误地允许非法值的逻辑有很大不同。例如,如果我正在验证用户输入的数据,如果我得到的值是不可接受的,则错误的来源是应用程序的最终用户。如果我得到一个null,这是程序员错误。
    • 无效值会导致堆栈溢出、内存不足错误、解析异常等。实际上,大多数错误通常在某些时候表现为某个方法调用中的无效值。因此,我认为IAE实际上是RuntimeException下所有异常中的最一般的
    • 李< / ul > < / >
    • 实际上,其他无效参数可能导致各种其他异常。UnknownHostExceptionFileNotFoundException,各种语法错误异常,IndexOutOfBoundsException,认证失败,等等,等等。

    一般来说,我觉得NPE受到了很大的诋毁,因为传统上一直与不遵循快速故障原理的代码联系在一起。再加上JDK未能用消息字符串填充NPE,这确实产生了一种强烈的负面情绪,这种情绪是没有根据的。实际上,从运行时的角度来看,NPE和IAE之间的区别仅限于名称。从这个角度来看,你的名字越精确,你给调用者的信息就越清晰。

我一直都赞成将IllegalArgumentException作为空参数抛出,直到今天,我注意到Java 7中的java.util.Objects.requireNonNull方法。用这种方法,而不是做:

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

你可以:

Objects.requireNonNull(param);

如果你传递的参数是null,它会抛出一个NullPointerException

考虑到该方法正好位于java.util的中间,我认为它的存在是一个非常强烈的迹象,表明抛出NullPointerException是“Java做事的方式”。

我想我已经决定了。

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

NullPointerException的另一个优点是,在高性能的关键代码中,您可以不需要显式检查null(以及带有友好错误消息的NullPointerException),而只依赖于在调用null参数的方法时自动获得的NullPointerException。如果你快速调用一个方法(即快速失败),那么你基本上有相同的效果,只是对开发人员来说不太友好。大多数情况下,显式检查并抛出有用的消息来指出哪个参数为null可能会更好,但是如果性能要求而不破坏方法/构造函数的已发布契约,那么可以选择更改该参数也很好。

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

throw new IllegalArgumentException(new NullPointerException(NULL_ARGUMENT_IN_METHOD_BAD_BOY_BAD));

一些集合假设使用NullPointerException而不是IllegalArgumentException拒绝null。例如,如果将包含null的集合与拒绝null的集合进行比较,第一个集合将在另一个集合上调用containsAll并捕获它的NullPointerException—而不是IllegalArgumentException。(我正在看AbstractSet.equals的实现。)

您可以合理地认为,以这种方式使用未经检查的异常是一种反模式,将包含null的集合与不包含null的集合进行比较可能会导致应该产生异常,或者将null放在集合中根本就是一个坏主意。尽管如此,除非您愿意说equals应该在这种情况下抛出异常,否则您只能记住NullPointerException在某些情况下是必需的,而在其他情况下则不需要。(“IAE”在NPE前面,“c”后面除外)

类似地,构建工具可能会插入空检查自动。值得注意的是,Kotlin的编译器这是否在向Java API传递一个可能为空的值时。当检查失败时,结果是NullPointerException。因此,为了给所有的Kotlin用户和Java用户提供一致的行为,您需要使用NullPointerException

抛出一个专属于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列表null, ImmutableList.of()2生成NullPointerException。然而,它碰巧生成了一个IllegalArgumentException,因为NullPointerTester碰巧使用了""的默认字符串。如果NullPointerTesternull的值只期望NullPointerException,它就会捕捉到错误。如果它期待IllegalArgumentException,它就会错过它。

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

  • 必须尽可能快地指出参数约束违反(-> fast fail),以避免难以调试的非法状态
  • 如果是一个无效的空指针,抛出NullPointerException
  • 如果数组/集合索引是非法的,抛出ArrayIndexOutOfBounds
  • 如果数组/集合大小为负,则抛出NegativeArraySizeException
  • 如果一个非法参数没有被上面所涵盖,并且你没有另一个更具体的异常类型,抛出IllegalArgumentException作为一个废纸篓
  • 另一方面,如果由于某些有效的原因,fast fail无法避免约束违反WITHIN a FIELD,则捕获并重新抛出IllegalStateException或更具体的检查异常。在这种情况下,永远不要让传递原始的NullPointerException, ArrayIndexOutOfBounds等!

至少有三个非常好的理由反对将所有类型的参数约束违反映射到IllegalArgumentException,第三个理由可能非常严重,以至于标志着这种做法的糟糕风格:

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

(2)映射异常实际上会导致另一种异常,由单继承引起:所有Java异常都是类,因此只支持单继承。因此,没有办法创建一个同时包含NullPointerException和IllegalArgumentException的异常,因为子类只能从其中一个继承。因此,在null参数的情况下抛出IllegalArgumentException使API用户更难区分问题,无论何时程序试图以编程方式纠正问题,例如通过将默认值提供给调用repeat!

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

另一方面,在标准实践中,规则保持简单,异常原因保持公开和具体。对于方法调用者,规则也很简单: -如果你遇到任何类型的文档运行时异常,因为你传递了一个非法的值,要么重复调用默认值(对于这种特定的异常是必要的),或者纠正你的代码 -另一方面,如果你遇到了一个运行时异常,对于给定的参数集,没有记录发生,文件错误报告给方法的制造者,以确保他们的代码或他们的文档是固定的

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

当试图访问具有当前值为null的引用变量的对象时抛出NullPointerException

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

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

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

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

NullPointerException:不故意抛出。npe只有在解引用空引用时才会被VM抛出。要尽一切可能的努力确保这些错误永远不会被抛出。@Nullable和@NotNull应该与代码分析工具一起使用来发现这些错误。

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

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

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

正如我所说,这个系统工作得非常好,有人解释了为什么会有这样的区别:“根据错误的类型,你很容易就能弄清楚该怎么做。即使您无法真正找出出错的地方,也可以找出在哪里捕获错误并创建额外的调试信息。”

NullPointerException:处理Null情况或放入断言,这样NPE就不会被抛出。如果你输入的断言只是另外两种类型中的一种。如果可能,继续调试,就像断言一开始就在那里一样。

IllegalArgumentException:你的调用站点有错误。如果传入的值来自另一个函数,请查明为什么接收到不正确的值。如果传入一个参数,则会在调用堆栈中进行错误检查,直到找到没有返回预期值的函数。

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

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