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