如果equals(null)抛出NullPointerException而不是一个坏主意?
equals
null
的合同如下:
对于任何非null的引用值
x
,x.equals(null)
应该return false
。
这是相当奇特的,因为如果o1 != null
和o2 == null
,那么我们有:
o1.equals(o2) // returns false o2.equals(o1) // throws NullPointerException
o2.equals(o1) throws NullPointerException
是一件好事,因为它提醒我们程序员错误。 然而,如果出于各种原因,我们只是将其切换到o1.equals(o2)
,那么这个错误就不会被o1.equals(o2)
。
所以问题是:
- 为什么
o1.equals(o2)
应该return false
而不是抛出NullPointerException
? - 如果可能的话,我们重写合约,以至于
anyObject.equals(null)
总是抛出NullPointerException
不是一个好主意?
与Comparable
相比之下,这是Comparable
合同所说的:
请注意,
null
不是任何类的实例,即使e.equals(null)
返回false
,e.compareTo(null)
应抛出NullPointerException
。
如果NullPointerException
适合于compareTo
,为什么不equals
?
相关问题
- Comparable和Comparator关于null的合同
一个纯粹的语义论证
这些是Object.equals(Object obj)
文档中的实际单词:
指示其他某个对象是否“等于”这一个。
什么是对象?
JLS 4.3.1对象
一个对象是一个类实例或一个数组。
引用值(通常只是引用 )是指向这些对象的指针,还有一个特殊的
null
引用,它引用没有对象 。
从这个angular度来看,我的论点很简单。
-
equals
testing其他某个对象是否“等于”this
-
null
引用不给testing的其他对象 - 因此,
equals(null)
应抛出NullPointerException
对于这种不对称性是否不一致的问题,我想不是,我把这个古代的禅宗提到:
- 问任何人,如果他和下一个人一样好,每个人都会说是。
- 询问任何人,如果他没有人,每个人都会说不。
- 不问任何人是否和任何人一样好,你永远不会得到答复。
那一刻,编译器达到了启发。
一个例外真的应该是一个特殊的情况。 空指针可能不是程序员错误。
你引用了现有的合同。 如果你决定违背惯例,毕竟这个时候,每个Java开发者都希望等于返回false,那么你将会做一些让你的class级变得贱民的意外和不受欢迎的东西。
我不能不同意。 我不会重写等于抛出exception。 如果我是客户的话,我会replace那些做的。
想想.equals与==有关,而.compareTo与比较运算符>,<,> =,<=相关。
如果你打算使用.equals来比较一个对象为null应该抛出一个NPE,那么你不得不说这个代码也应该抛出一个:
Object o1 = new Object(); Object o2 = null; boolean b = (o1 == o2); // should throw NPE here!
o1.equals(o2)和o2.equals(o1)之间的区别在于,在第一种情况下,您将某些内容与null进行比较,类似于o1 == o2,而在第二种情况下, equals方法从未实际执行所以根本没有比较。
关于.compareTo契约,比较一个非null对象和一个null对象就像是这样做:
int j = 0; if(j > null) { ... }
显然这不会编译。 您可以使用自动拆箱来进行编译,但是在进行比较时会得到一个NPE,这与.compareTo合同一致:
Integer i = null; int j = 0; if(j > i) { // NPE ... }
这并不是说这个问题一定会成为你的问题的答案,它只是一个当我觉得这个行为现在是如何有用的例子。
private static final String CONSTANT_STRING = "Some value"; String text = getText(); // Whatever getText() might be, possibly returning null.
现在我可以做到。
if (CONSTANT_STRING.equals(text)) { // do something. }
我没有机会得到一个NullPointerException。 如果按照你的build议改变了,我会回到必须做的:
if (text != null && text.equals(CONSTANT_STRING)) { // do something. }
这是一个足够的理由,因为这是行为? 我不知道,但这是一个有用的副作用。
如果把面向对象的概念考虑进去,并考虑整个发送者和接收者的angular色,我会说这种行为是方便的。 在第一种情况下,你看问题是否等于没有人。 他应该说“不,我不是”。
在第二种情况下,你没有提及任何人所以你不是真的问任何人。 这应该抛出一个例外,第一种情况不应该。
我认为如果你忘记了面向对象,并把expression看作一个math的平等,那么它就是不对称的。 然而,在这个范例中,两个angular色扮演着不同的angular色,所以可以预料到秩序是重要的。
作为最后一点。 当您的代码出现错误时,应该引发空指针exception。 但是,如果他不是一个人,就不应该被认为是一个编程缺陷。 我认为,如果他不是空的,就可以问一个对象。 如果您不控制为您提供对象的源代码,该怎么办? 并且这个源向你发送null。 你会检查对象是否为空,只有后来看它们是否是平等的? 如果仅仅比较两者,不pipe第二个目标是什么比较,都不例外地进行比较。
诚实地说,如果一个在它的主体中的equals方法有意地返回一个空指针exception,我会很生气。 等于是用来反对任何forms的对象,所以它不应该如此挑剔它收到的东西。 如果一个equals方法返回了npe,那么我认为最后一件事就是这样做。 特别考虑到这是一个未经检查的例外。 如果你确实提出了一个窍门,一个人必须记得在调用你的方法之前总是检查null,或者更糟糕的是,在try / catch块中围绕着调用equals(上帝,我讨厌try / catch块)。 ..
就个人而言,我宁愿它的performance。
NullPointerException
标识问题出现在执行equals操作的对象中。
如果NullPointerException
被用于你的build议,你尝试了(有点毫无意义的)操作…
o1.equals(o1)
其中o1 = null …抛出NullPointerException
是因为你的比较函数被搞乱了,还是因为o1是null,但是你没有意识到? 我知道一个极端的例子,但是现在的行为,我觉得你可以很容易地知道问题出在哪里。
在第一种情况下, o1.equals(o2)
返回false,因为o1
不等于o2
,这非常好。 在第二种情况下,它会抛出NullPointerException
因为o2
为null
。 一个不能调用null
任何方法。 一般来说,这可能是编程语言的一个限制,但我们必须忍受它。
抛出NullPointerException
是违反equals
方法的合同并使事情变得比原来更复杂也不是一个好主意。
有很多常见的情况, null
并不是特例,例如它可能仅仅代表一个密钥没有价值的情况(非例外),或者代表“无”。 因此,做一个未知y
x.equals(y)
也是很常见的事情,不得不总是首先检查null
这只不过是浪费了精力。
至于为什么null.equals(y)
是不同的, 在Java的空引用中调用任何实例方法是一个编程错误,因此值得一个例外。 x.equals(y)
的x
和y
的sorting应该select为使x
已知不为null
。 我认为在几乎所有的情况下,这种重新sorting可以基于事先已知的对象来完成(例如,根据它们的来源,或者通过针对其他方法调用来检查null
)。
同时,如果两个对象都是未知的“nullness”,那么其他代码几乎肯定需要检查它们中的至less一个,或者不用冒着NullPointerException
冒险的对象可以做很多事情。
由于这是指定的方式,因此打破合约并为equals
null
参数引发exception是一个编程错误。 如果您考虑要求抛出exception的替代方法,那么每个equals
实现都必须作出特殊的说明,每个调用equals
()的对象都必须在调用之前进行检查。
它可能有不同的规定(即equals
的先决条件将要求参数是非null
),所以这并不是说你的论证是无效的,但是现在的规范使得编程语言更简单和更实用。
请注意,合同是“对于任何非空的参考x”。 所以实现将如下所示:
if (x != null) { if (x.equals(null)) { return false; } }
x
不必为null
,因为以下等式的定义是可能的:
public boolean equals(Object obj) { // ... // If someMember is 0 this object is considered as equal to null. if (this.someMember == 0 and obj == null) { return true; } return false; }
我认为这是关于方便,更重要的是一致性 – 允许空值作为比较的一部分,避免了必须执行null
检查并实现每次调用equals
的语义。 null
引用在许多集合types中是合法的,因此它们可以作为比较的右侧出现。
使用实例方法进行平等,比较等,必然会使得这种安排不对称 – 对于多态性的巨大收益有点麻烦。 当我不需要多态时,我有时会创build一个带有两个参数MyObject.equals(MyObjecta, MyObject b)
的对称静态方法。 这个方法然后检查一个或两个参数是否为空引用。 如果我特别想排除空引用,那么我创build了一个额外的方法,例如equalsStrict()
或类似的方法,它在委托给其他方法之前进行空检查。
这是一个棘手的问题。 为了向后兼容,你不能这样做。
想象下面的情况
void m (Object o) { if (one.equals (o)) {} else if (two.equals (o)) {} else {} }
现在与等于返回false else子句将得到执行,但不是当抛出一个exception。
同样,null并不等于说“2”,所以返回false是非常有意义的。 那么坚持null.equals(“b”)也可能返回false :))
但是这个要求确实会造成一个奇怪而不对称的等价关系。