【问题标题】:Is it a bad idea if equals(null) throws NullPointerException instead?如果 equals(null) 抛出 NullPointerException 是一个坏主意吗?
【发布时间】:2011-02-22 16:26:29
【问题描述】:

equalsnull的合约如下:

对于任何非空引用值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) 应该抛出一个NullPointerException,即使e.equals(null) 返回false

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

相关问题


纯语义论证

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

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

什么是对象?

JLS 4.3.1 Objects

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

引用值(通常只是references)是指向这些对象的指针,以及一个特殊的null 引用,没有引用任何对象

这个角度我的论点很简单。

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

【问题讨论】:

  • 在此评论说,众所周知,在 Java 中,equals() 出现在 OO 层次结构的最顶层,这是不可能 尊重除了最简单的情况以外的任何事情的等价合同(即当你根本不做 OO 时)。认为存在诸如未损坏的 Java equals() 合约之类的东西是一种妄想。我们走得更远:默认情况下 equals()hashCode() 会抛出 UOE。如果您想使用这些方法,您必须记录您如何处理此处提出的基本问题:artima.com/lejava/articles/equality.html
  • 8 票和 3 个最爱我的问题与 equals 的无可争辩的破碎有关:stackoverflow.com/questions/2205565 问题是:“共同等于智慧”根本行不通.不仅像 Joshua Bloch 和 Martin Odersky 这样的人这么说,而且你可以用逻辑来证明这一事实。您根本无法进行 OOA/OOD 到 OOP 的转换,并希望重用 Java 的相等概念:对我来说,equals 存在于 Object 中是语言的一个根本缺陷。当然,喝 Gosling kool-aid 的人会不同意。让他们和布洛赫争论
  • 我的最后一点是:在许多情况下,这不是关于抛出 NPE 或返回 false:而是关于抛出一个巨大的 UnsupportedOperationException 并且它是一个 Java 缺陷,允许在对象上调用 equals,这些对象一开始就不应该有相等的概念。著名的遗言:UnsupportedOperationException :)

标签: java null equals nullpointerexception


【解决方案1】:

关于这种不对称是否不一致的问题,我认为不是,我请你参考这个古老的禅宗:

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

那一刻,编译器开悟了。

【讨论】:

  • 我想知道 Java 编程语言的设计者是否真的想到了这一点,或者他们只是没有注意到不对称性...... ;-)
  • Koan 不拼写为 ko-an,显然 o 上有一个长音符号。
  • 哈好吧,我会修复它,有趣的是,这几乎是我最受欢迎的答案。必须做对。
【解决方案2】:

异常确实应该是异常的情况。空指针可能不是程序员错误。

您引用了现有合同。如果您决定违反约定,那么在所有 Java 开发人员都希望 equals 返回 false 时,您将做一些意想不到的不受欢迎的事情,这将使您的类成为贱民。

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

【讨论】:

  • 我喜欢问题中的推理(如果它是null,则不能使用变量)-但@duffymo 是对的,对他的回答的支持来自Java 代码本身如何实现@ 987654322@ 用于许多类(查看源代码)。首先使用 == 检查相等性,然后使用 instanceOf 检查不等式。根据定义,这两个检查都将返回一个没有NullPointerException 的布尔值——即,在证明它不是null 之前,不会在.equals() 方法中使用给定的对象。因此,只要有可能,您应该编写自己的类以使其行为方式相同。
【解决方案3】:

想想 .equals 与 == 的关系,而 .compareTo 与比较运算符 >、=、

如果您要争辩说使用 .equals 将对象与 null 进行比较应该抛出 NPE,那么您必须说这段代码也应该抛出 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 合约,将非空对象与空对象进行比较就像尝试这样做:

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

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

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

【讨论】:

  • 我认为,当涉及到对象时,相等 (==) 和关系 (, =) 运算符之间存在根本区别。关系对象不将对象作为操作数(即使它们属于同一类型),因此使用 compareTo 方法。另一方面,equals 方法不能替代相等运算符。相等运算符仍然可以用于对象(假设它们属于同一类型),并且与 equals 方法的用途不同。
【解决方案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.
}

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

【讨论】:

    【解决方案5】:

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

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

    我认为只有当您忘记面向对象并将表达式视为数学等式时,它才是不对称的。然而,在这个范式中,两端扮演着不同的角色,因此可以预料到顺序很重要。

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

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

    【讨论】:

    • “你与任何人平等吗”这个问题有点可疑。另一方面,equals 没有被赋予一个object——它被赋予了一个reference 对象。问题实际上是“此参考是否标识了与您相同的对象”。这样的问题可以很容易地回答“不——因为该引用没有识别任何对象,它肯定没有识别与我等效的对象”。
    • 请进一步注意,重新表述的问题证明了涉及任何对象类型组合的相等比较是正确的。被问到“你是否等同于红色”的人可能会认为该问题格式不正确,但“此引用 [标识红色] 是否指代一个等同于您的对象”不是。该引用标识了与该人不等效的事物,因此答案为“否”。
    • 我理解你所做的改变,我同意。但在实践中,你只是用更准确和技术性的术语说了我的话。如果您从我的回答中得到相反的想法,那么也许我表达得不好。
    • 你表达得很好;我的意思是建议对这个问题进行明确的改写,即使引用为空,也显然没有问题。太糟糕了,没有简洁的符号来对一对对象执行“等于”检查,其中一个或两个都可能为空。
    • 我希望在现代面向对象语言中看到的一件事,顺便说一句,是一种在声明或句法上区分引用封装对象的身份的情况的方法,它封装了 values 的各种情况(能够区分共享与非共享、可变与只读与不可变、安全变异与请勿打扰的不同组合) ,以及偶尔引用应该是其自身的实体的情况。 Java 在这方面非常薄弱,不幸的是 .NET 借鉴了它的大部分弱点。
    【解决方案6】:

    就我个人而言,我宁愿它的表现如此。

    NullPointerException 表明问题出在执行等于操作的对象中。

    如果 NullPointerException 已按照您的建议使用,并且您尝试了...的(某种毫无意义的)操作

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

    【讨论】:

      【解决方案7】:

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

      抛出NullPointerException 也不是一个好主意,你违反了equals 方法的约定,并使事情变得比它必须的更复杂。

      【讨论】:

        【解决方案8】:

        在许多常见情况下,null 并不例外,例如它可能只是表示键没有值的(非异常)情况,或者代表“无”。因此,用未知的y 执行x.equals(y) 也很常见,而必须始终先检查null 只会浪费精力。

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

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

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

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

        【讨论】:

        • 您实际上可以在空引用上调用静态方法,因此您的答案并不完全准确。不用说,这是一件非常糟糕的事情。
        【解决方案9】:

        请注意,合同是“针对任何非空引用 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】:

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

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

          【讨论】:

            【解决方案11】:

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

            要表明这是标准,请参阅来自java.util 的'Objects.equals(Object, Object),它仅对第一个参数执行非对称空检查(将调用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 值视为相等。

            【讨论】:

              【解决方案12】:

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

              想象以下场景

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

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

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

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2015-08-25
                • 2012-04-02
                • 1970-01-01
                • 1970-01-01
                • 2020-12-12
                • 1970-01-01
                • 2011-10-10
                • 1970-01-01
                相关资源
                最近更新 更多