关于Java:如果equals(null)抛出Null PoExtExtExchange,这是不是一个坏主意?

Is it a bad idea if equals(null) throws NullPointerException instead?

equalsnull的合同如下:

For any non-null reference value x, x.equals(null) should return false.

这是很特别的,因为如果o1 != nullo2 == null的话,我们有:

1
2
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合同规定:

Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false.

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

相关问题

  • 关于无效的可比较和比较契约

纯粹的语义论证

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

Indicates whether some other object is"equal to" this one.

什么是物体?

JLS 4.3.1对象

An object is a class instance or an array.

The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.

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

  • equals测试其他对象是否"等于"this
  • null参考文献未给出试验的其他对象。
  • 因此,equals(null)应该抛出NullPointerException


关于这种不对称性是否不一致的问题,我认为不是,我指的是这个古老的禅k_安:

  • 问任何人他是否和下一个人一样优秀,每个人都会说是的。
  • 问任何人他是否和任何人一样好,每个人都会说不。
  • 没人问它是否和任何人一样好,你永远不会得到答复。

在那一刻,编纂者达到了启蒙。


例外情况真的应该是例外情况。空指针可能不是程序员错误。

你引用了现有的合同。如果你决定违背惯例,在这段时间之后,当每个Java开发者都期望返回false时,你会做一些意想不到的、不受欢迎的事情,这会让你的班成为一个贱民。

我再也不能不同意了。我不会重写等于一直抛出一个异常。如果我是它的客户,我会替换任何这样做的类。


想想.equals与==和.compareTo是如何关联的,compareTo与比较运算符>、<、>=、<=。

如果你要争论使用.equals将一个对象与空值进行比较会引发一个NPE,那么你必须说这段代码也会引发一个NPE:

1
2
3
Object o1 = new Object();
Object o2 = null;
boolean b = (o1 == o2); // should throw NPE here!

o1.equals(o2)和o2.equals(o1)的区别在于,在第一种情况下,您将某些内容与空进行比较,类似于o1==o2,而在第二种情况下,实际上从未执行equals方法,因此根本没有进行比较。

关于.compareto约定,将非空对象与空对象进行比较就像尝试执行以下操作:

1
2
3
4
int j = 0;
if(j > null) {
   ...
}

显然,这不会编译。您可以使用自动拆箱进行编译,但进行比较时会得到一个NPE,这与.compareto约定一致:

1
2
3
4
5
Integer i = null;
int j = 0;
if(j > i) { // NPE
   ...
}


如果考虑到面向对象的概念,并考虑到整个发送者和接收者角色,那么我认为行为是方便的。在第一种情况下,你问的是一个物体,他是否等于任何人。他应该说"不,我不是"。

但在第二种情况下,你没有提到任何人,所以你没有真正问任何人。这应该引发一个例外,第一个案例不应该。

我认为只有当你忘记了对象的方向,把表达式当作数学等式时,它才是不对称的。然而,在这种模式中,双方都扮演着不同的角色,所以可以预期,秩序是重要的。

最后一点。当代码中存在错误时,应引发空指针异常。但是,询问一个对象是否是一个无名小卒,不应被视为编程缺陷。我想问问一个物体是否为空是完全可以的。如果您不控制为您提供对象的源代码怎么办?这个消息源向您发送了空值。您会检查对象是否为空,然后才查看它们是否相等吗?仅仅比较两个对象会更直观吗?无论第二个对象是什么,都会毫无例外地进行比较?

老实说,如果其主体中的equals方法故意返回空指针异常,我会很生气。等号是用来对付任何一种物体的,所以它不应该对它接收到的东西那么挑剔。如果一个equals方法返回了npe,我想最后一件事就是它是故意这样做的。特别考虑到这是一个未经检查的例外。如果你真的提升了一个NPE,那么一个人在调用你的方法之前必须记住总是检查空值,或者更糟的是,将对equals的调用包围在一个try/catch块中(上帝我讨厌try/catch块),但是哦,好吧……


不是说这是对你问题的一个必要的回答,而是一个例子,当我发现这是有用的,行为是现在的样子。

1
2
private static final String CONSTANT_STRING ="Some value";
String text = getText();  // Whatever getText() might be, possibly returning null.

就目前情况而言,我能做到。

1
2
3
if (CONSTANT_STRING.equals(text)) {
    // do something.
}

我没有机会得到空指针异常。如果按照您的建议进行了更改,我将不得不重新开始:

1
2
3
if (text != null && text.equals(CONSTANT_STRING)) {
    // do something.
}

这是一个很好的理由使行为保持原样吗?我不知道,但这是一个有用的副作用。


在许多常见情况下,null以任何方式都不例外,例如,它可以简单地表示(非例外)情况,即键没有值,或者表示"无"。因此,用一个未知的y来做x.equals(y)也是很常见的,必须总是先检查null是浪费精力。

至于为什么EDCOX1与4是不同的,在Java中调用空引用的任何实例方法都是一个编程错误,因此值得一个例外。应选择x.equals(y)xy的顺序,以便知道x不是null。我认为,在几乎所有的情况下,这种重新排序都可以基于预先知道的对象的情况(例如,从对象的原始位置,或者通过检查其他方法调用的null)来完成。

同时,如果这两个对象都是未知的"空",那么其他代码几乎肯定需要检查其中的至少一个对象,或者在不冒NullPointerException风险的情况下,对该对象不能执行太多操作。

由于这是规定的方式,因此违背合同并对equalsnull参数提出异常是编程错误。如果您考虑要求抛出异常的替代方法,那么每个equals的实现都必须对它进行特殊处理,并且每个调用equals的对象都必须在调用之前进行检查。

它可能是以不同的方式指定的(即,equals的前提条件要求参数为非null,因此这并不是说您的论证是无效的,但当前的规范使编程语言更简单、更实用。


在第一种情况下,o1.equals(o2)返回错误,因为o1不等于o2,这是完全正确的。在第二种情况下,它抛出NullPointerException,因为o2null。不能对null调用任何方法。一般来说,它可能是编程语言的一个限制,但我们必须接受它。

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


就我个人而言,我更希望它能像它那样发挥作用。

NullPointerException确定问题在执行equals操作的对象中。

如果按照你的建议使用NullPointerException并且你尝试了(某种意义上的)操作…

o1.equals(o1),其中o1=空…是因为比较函数是螺纹的还是因为o1是空的,但你没有意识到,所以抛出了NullPointerException?我知道,这是一个极端的例子,但根据目前的行为,我觉得你可以很容易地分辨出问题所在。


请注意,本合同为"任何非空参考X"。因此,实现过程如下:

1
2
3
4
5
if (x != null) {
    if (x.equals(null)) {
        return false;
    }
}

x不必被视为null等于null,因为equals的以下定义是可能的:

1
2
3
4
5
6
7
8
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;
}

我认为这是为了方便和更重要的一致性——允许空值作为比较的一部分,避免了每次调用equals时都必须进行null检查和实现其语义。null引用在许多集合类型中都是合法的,因此它们可以作为比较的右侧出现是有意义的。

使用实例方法进行相等、比较等,必然会使排列不对称——这对于多态性的巨大增益来说有点麻烦。当我不需要多态性时,我有时会创建一个带有两个参数的对称静态方法,MyObject.equals(MyObjecta, MyObject b)。然后,此方法检查一个或两个参数是否为空引用。如果我特别想排除空引用,那么我创建了一个额外的方法,例如equalsStrict()或类似的方法,在委托给另一个方法之前执行空检查。


这是一个棘手的问题。对于向后兼容,您不能这样做。

想象一下下面的场景

1
2
3
4
5
void m (Object o) {
 if (one.equals (o)) {}
 else if (two.equals (o)) {}
 else {}
}

现在,使用equals返回false else子句将被执行,但在引发异常时不会执行。

同样,空值并不等于说"2",所以返回false是完全有意义的。那么,最好坚持null.equals("b")返回同样为false:)

但这一要求确实产生了一个奇怪的非对称的等量关系。