如果equals(null)抛出NullPointerException而不是一个坏主意?

equals null的合同如下:

对于任何非null的引用值xx.equals(null)应该return false

这是相当奇特的,因为如果o1 != nullo2 == 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)返回falsee.compareTo(null)应抛出NullPointerException

如果NullPointerException适合于compareTo ,为什么不equals

相关问题

  • Comparable和Comparator关于null的合同

一个纯粹的语义论证

这些是Object.equals(Object obj)文档中的实际单词:

指示其他某个对象是否“等于”这一个。

什么是对象?

JLS 4.3.1对象

一个对象是一个类实例或一个数组。

引用值(通常只是引用 )是指向这些对象的指针,还有一个特殊的null引用,它引用没有对象

从这个angular度来看,我的论点很简单。

  • equalstesting其他某个对象是否“等于” 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因为o2null 。 一个不能调用null任何方法。 一般来说,这可能是编程语言的一个限制,但我们必须忍受它。

抛出NullPointerException是违反equals方法的合同并使事情变得比原来更复杂也不是一个好主意。

有很多常见的情况, null并不是特例,例如它可能仅仅代表一个密钥没有价值的情况(非例外),或者代表“无”。 因此,做一个未知y x.equals(y)也是很常见的事情,不得不总是首先检查null这只不过是浪费了精力。

至于为什么null.equals(y)是不同的, 在Java的空引用调用任何实例方法一个编程错误,因此值得一个例外。 x.equals(y)xy的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 :))

但是这个要求确实会造成一个奇怪而不对称的等价关系。