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


75

合同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)不应该return false抛出一个好主意NullPointerException呢?
  • 如果我们尽可能重写合同以改为anyObject.equals(null)始终抛出,那将是一个坏主意NullPointerException吗?

与比较 Comparable

相比之下,Comparable合同是这样说的:

请注意,null不是任何类的实例,并e.compareTo(null)应抛出NullPointerException,即使e.equals(null)回报false

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

相关问题


一个纯粹的语义论点

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

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

什么是物体?

JLS 4.3.1对象

一个对象是一个类的实例或阵列。

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

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

  • equals测试其他对象是否“等于”this
  • null参考没有给出其他测试对象
  • 因此,equals(null)应该扔NullPointerException

2
在这里评论说,众所周知的事实是,在Java中,在OO层次结构的最顶层具有equals()的情况下,除了最简单的情况(即,当您不执行操作时),对于任何其他情况都不可能遵守equals契约。面向对象)。认为存在不间断的Java equals()契约之类的事情是一种幻想。我们走得更远:默认情况下equals()hashCode()抛出UOE。如果要使用这些方法,则必须在此处记录如何处理所提出的基本问题:artima.com/lejava/articles/equality.html
SyntaxT3rr0r 2010年

1
我的问题有8票和3则最喜欢的问题,与这里无可置疑的平等断裂有关:stackoverflow.com/questions/2205565事情是:“共同平等智慧”根本行不通。不仅像约书亚·布洛赫(Joshua Bloch)和马丁·奥德斯基(Martin Odersky)这样的人都在说这句话,而且您可以使用逻辑来证明这一事实。您根本无法执行OOA / OOD到OOP的转换,并希望重用Java的相等概念:对我来说,这是Object中存在的equal语言中的一个基本缺陷。当然,喝戈斯林助剂的人会不同意。让他们与Bloch争论
SyntaxT3rr0r 2010年

1
我的最后一点是:在很多情况下,这与抛出NPE或返回false无关:与引发巨大的UnsupportedOperationException异常有关,这是Java的缺陷,它允许在不具有相等性概念的对象上调用equals第一名。著名的最后一句话: UnsupportedOperationException :)
语法T3rr0r,2010年

Answers:


105

关于这种不对称是否不一致的问题,我认为不是,我将您引荐给这个古老的禅宗禅:

  • 问任何一个男人是否和下一个男人一样好,每个人都会说“是”。
  • 询问任何人是否与没有人一样好,每个人都会拒绝。
  • 没人问它是否比男人好,您将永远不会得到答复。

在那一刻,编译器得到启发。


6
我想知道Java编程语言的设计师是否真的想到了这一点,或者他们只是没有注意到不对称... ;-)
Jesper,2010年

1
Koan没有被拼写为ko-an,显然o上有一个马克龙。
数字无常

好吧,我将修复它,因为可笑的是,这几乎是我最受欢迎的答案。必须正确。
肖恩·欧文

19

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

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

我完全不同意。我不会重写等于始终抛出异常的方法。如果我是它的客户,那么我将替换所有这样做的类。


我喜欢问题中的推理(如果是,则不能使用变量null)-但是@duffymo是正确的,对他答案的支持来自Java代码本身如何实现.equals()许多类的功能(请参见源代码)。首先使用==检查相等性,然后使用instanceOf检查不平等性。根据定义,这两个检查都将返回一个不带布尔值的布尔值NullPointerException-即,.equals()在证明给定对象为non-之前,不使用该给定对象null。因此,只要有可能,就应该编写自己的类以使其行为相同。
PMorganCA 2014年

8

考虑.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合同,将非null对象与null对象进行比较就像尝试执行以下操作:

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

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

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

我认为在对象方面,等号(==)和关系(<,>,<=,> =)运算符之间存在根本区别。关系对象不会将对象作为操作数(即使它们的类型相同),因此可以使用compareTo方法。另一方面,equals方法不能替代equals运算符。相等运算符仍可用于对象(只要它们具有相同的类型),并且与equals方法相比具有不同的用途。
Shazz

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

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

但是在第二种情况下,您没有引用任何人,因此您并不是真正在问任何人。这应该引发异常,第一种情况不应该。

我认为,如果您忘了面向对象并将表达式视为数学等式,那只会是不对称的。但是,在这种范例中,两端扮演着不同的角色,因此可以预期顺序很重要。

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

老实说,如果它体内的一个equals方法故意返回一个空指针异常,我会很生气。Equals可以用于任何种类的对象,因此它对接收到的内容不应太挑剔。如果equals方法返回npe,我想到的最后一件事就是它是故意这样做的。特别考虑到这是一个未经检查的异常。如果确实提高了npe,则一个家伙必须记住在调用您的方法之前始终检查null,或更糟糕的是,将对等号的调用包含在try / catch块中(上帝,我讨厌try / catch块),但是,哦,好。 ..


1
“您等于没有人”这个问题有些可疑。另一方面,equals没有提供对象,而是提供了对象引用。问题实际上是“此引用是否标识与您相同的对象”。可以很容易地回答这样一个问题:“不,因为引用没有标识任何对象,所以它当然也没有标识与我等效的对象”。
2013年

还要注意,重新表述的问题证明了涉及对象类型任何组合的相等比较是正确的。有人问“您是否等于红色”,可能会认为该问题格式不正确,但“此引用(标识红色)是否引用了与您等效的对象”呢?该参考标识出与该人不等同的事物,因此答案为“否”。
2013年

我了解您所做的改变,我也同意。但是在实践中,您只是用更准确和专业的术语说了我的意思。如果您从我的回答中得到相反的想法,那么也许我表达的很差。
arg20 2013年

你表现得很好。我的观点是建议对这个问题进行明确的表述,即使使用空引用,也显然没有问题。太糟糕了,没有对一对对象执行“相等”检查的简洁表示法,这两个对象中的一个或两个都可能为null。
2013年

我想用现代的面向对象语言btw看到的一件事是一种声明式或句法上区分引用封装对象标识的情况的方法,各种情况下引用封装值的情况(具有区分的能力)共享与非共享,可变与只读,不可变,安全突变与请勿打扰的不同组合)以及偶尔引用应该是实体的情况。Java在这方面非常薄弱,不幸的是.NET借鉴了它的许多弱点。
2013年

2

就个人而言,我宁愿它像它一样执行。

NullPointerException标识,这个问题是在与正在执行equals操作的对象。

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

o1.equals(o1)其中o1 = null ...是NullPointerException因为比较函数被拧紧还是因为o1为null但您没有意识到而抛出该错误?我知道这是一个极端的例子,但是根据当前的行为,我觉得您可以轻松地判断问题出在哪里。


2

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

NullPointerException您违反该equals方法的约定并使事情变得比原来复杂的想法也不是一个好主意。


2

在许多常见情况下,它们null并没有任何例外,例如,它可能仅表示密钥没有值的(非例外)情况,或者代表“无”。因此,x.equals(y)处理未知数y也很常见,而必须始终先进行检查null只是浪费精力。

至于为什么null.equals(y)不同,在Java中对空引用调用任何实例方法编程错误,因此值得例外。应该选择in和in的顺序,使之不是已知的。我会争辩说,在几乎所有情况下,都可以基于对象的已知信息(例如,从它们的来源,或者通过检查其他方法调用)来进行重新排序。xyx.equals(y)xnullnull

同时,如果两个对象都具有未知的“空”,那么几乎可以肯定,其他代码需要检查其中至少一个,否则就可以在不冒风险的情况下对该对象进行很多操作NullPointerException

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

可以用不同的方式指定它(即,的前提是equals要求参数为非null),因此这并不是说您的参数无效,而是当前的说明使一种更简单,更实用的编程语言成为可能。


实际上,您可以在null引用上调用静态方法,因此您的答案并不完全准确。不用说,这是一件非常糟糕的事情。
CurtainDog 2013年

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

1

我认为这是关于便利性,更重要的是一致性–允许将null用作比较的一部分,从而避免了必须进行null检查并实现每次equals调用的语义。null引用在许多集合类型中都是合法的,因此可以将它们显示为比较的右侧是有意义的。

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


0

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

想象以下情况

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

现在,返回等于false的else子句将被执行,但是抛出异常时则不会执行。

同样,null并不真正等于说“ 2”,因此返回false完全有意义。那么最好坚持使用null.equals(“ b”)返回false :))

但是,这一要求的确形成了奇怪且非对称的等式关系。


0

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

为了表明这是标准,请参阅' Objects.equals(Object, Object)from 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相等的值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.