如果equals(null)抛出NullPointerException,这是一个坏主意吗?

时间:2010-05-22 10:45:50

标签: java null equals nullpointerexception

equals关于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)应该return false而不是投掷NullPointerException为什么是个好主意?
  • 如果我们尽可能重写合同以便anyObject.equals(null)总是抛出NullPointerException,那会不是一个坏主意?

Comparable

进行比较

相反,这就是Comparable contract所说的:

  

请注意,null不是任何类的实例,即使e.compareTo(null)返回NullPointerExceptione.equals(null)也应该抛出false

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

相关问题


纯粹的语义论证

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

  

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

什么是对象?

JLS 4.3.1 Objects

  

对象类实例或数组。

     

参考值(通常只是引用)是指向这些对象的指针,以及一个特殊的null引用,引用无对象

从这个角度来看我的论点非常简单。

  • equals测试某些其他对象是否“等于”this
  • null引用没有为测试提供其他对象
  • 因此,equals(null)应该抛出NullPointerException

12 个答案:

答案 0 :(得分:102)

关于这种不对称是否不一致的问题,我想不是,我推荐你到这个古老的禅宗:

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

那时,编制者达到了启蒙。

答案 1 :(得分:19)

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

您引用了现有合同。如果您决定违反惯例,那么在所有这些时间之后,当每个Java开发人员都期望等于返回false时,您将会做出意想不到的事情并且不受欢迎,这将使您的课程成为一个贱民。

我不能不同意。我不会重写equals来一直抛出异常。如果我是客户,我会替换任何那样做的课程。

答案 2 :(得分:8)

考虑.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,而在第二种情况下,等于方法从未实际执行因此根本没有比较发生。

关于.compareTo契约,将非null对象与null对象进行比较就像尝试这样做:

int j = 0;
if(j > null) { 
   ... 
}

显然这不会编译。您可以使用自动拆箱来进行编译,但是在进行比较时会得到NPE,这与.compareTo合同一致:

Integer i = null;
int j = 0;
if(j > i) { // NPE
   ... 
}

答案 3 :(得分:4)

并不是说这对您的问题是一个必然的答案,它只是我发现行为现在如何有用的一个例子。

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。如果它按照你的建议改变了,我会回来做:

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

这是一个足够的理由让行为成为现实吗?我不知道,但这是一个有用的副作用。

答案 4 :(得分:4)

如果考虑面向对象的概念,并考虑整个发送者和接收者角色,我会说这种行为很方便。在第一种情况下,你会问一个对象是否等于没有人。他应该说“不,我不是”。

在第二种情况下,你没有引用任何人所以你并不是真的在问任何人。这应该抛出异常,第一种情况不应该。

我认为如果你忘记了面向对象并将表达视为数学上的平等,那么它只是不对称的。然而,在这种范式中,两端都扮演着不同的角色,因此可以预期秩序很重要。

最后一点。当代码中出现错误时,应引发空指针异常。但是,如果一个对象是无人问的话,不应该被认为是一个编程缺陷。我认为问一个对象是否不为空是完全可以的。如果您不控制为您提供对象的源,该怎么办?这个源发送给你null。你会检查对象是否为空,然后才检查它们是否等于?仅仅比较两者并不是更直观的,无论第二个对象是什么,比较都将毫无例外地进行?

老实说,如果其主体内的equals方法故意返回空指针异常,我会很生气。 Equals用于对付任何类型的对象,因此它不应该如此挑剔。如果一个equals方法返回npe,我脑海中最后一件事就是它故意这样做了。特别考虑它是一个未经检查的例外。如果你确实提高了一个人,你必须记住在调用你的方法之前总是检查null,或者更糟的是,在try / catch块中围绕调用equals(上帝我讨厌try / catch块)但是哦。 ..

答案 5 :(得分:2)

就个人而言,我宁愿表现得像它一样。

NullPointerException标识问题出在正在执行equals操作的对象中。

如果按照您的建议使用了NullPointerException并尝试了(有点无意义)操作......

o1.equals(o1)其中o1 = null ... 引发NullPointerException是因为你的比较函数被搞砸了还是因为o1是null但是你没有意识到? 我知道,这是一个极端的例子,但是根据当前的行为,我觉得你可以很容易地说出问题所在。

答案 6 :(得分:2)

在第一种情况下,o1.equals(o2)返回false,因为o1不等于o2,这非常好。在第二种情况下,它会抛出NullPointerException,因为o2null。无法在null上调用任何方法。它可能是编程语言的一般限制,但我们必须忍受它。

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

答案 7 :(得分:2)

有许多常见情况null在任何情况下都不例外,例如它可以简单地表示一个键没有价值的(非例外)情况,或者代表“没有”的情况。因此,使用未知x.equals(y)执行y也很常见,并且必须始终首先检查null才会浪费精力。

至于为什么null.equals(y)不同, 是一个编程错误,无法在Java 上调用任何实例方法>,因此值得例外。应选择xyx.equals(y)的排序,以便知道x不是null。我认为,在几乎所有情况下,这种重新排序都可以基于事先已知的对象(例如,从它们的起源,或通过检查null进行其他方法调用)来完成。

同时如果两个对象都是未知的“nullness”,那么其他代码几乎肯定需要至少检查其中一个,或者对该对象做的不多,而不会冒NullPointerException的风险。

由于这是指定的方式,因此打破合同并将null参数的异常引发到equals是一个编程错误。如果您考虑要求抛出异常的替代方案,那么equals的每个实现都必须对其进行特殊处理,并且每次调用equals时都可能null在调用之前,对象必须检查。

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

答案 8 :(得分:1)

我认为它是关于方便性,更重要的是一致性 - 允许空值成为比较的一部分,避免必须进行null检查并实现每次调用equals时的语义。 null引用在许多集合类型中都是合法的,因此它们可以显示为比较的右侧。

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

答案 9 :(得分:1)

请注意,合同是“对于任何非空引用x”。所以实现将如下所示:

if (x != null) {
    if (x.equals(null)) {
        return false;
    }
}

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

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;
}

答案 10 :(得分:0)

这是一个棘手的问题。为了向后兼容,你不能这样做。

想象一下以下场景

void m (Object o) {
 if (one.equals (o)) {}
 else if (two.equals (o)) {}
 else {}
}

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

null也不等于说“2”,所以返回false是完全合理的。那么最好坚持null.equals(“b”)也返回false:))

但是这个要求确实会产生一种奇怪的,非对称的等于关系。

答案 11 :(得分:0)

如果参数为false,则应返回null

要表明这是标准,请参阅Objects.equals(Object, Object)中的'java.util,该命令仅对第一个参数(将调用equals(Object))执行不对称的空检查。从OpenJDK SE 11源代码(SE 1.7包含的内容完全相同):

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

这同样处理两个null值。