有什么理由在生成.equals()时更喜欢getClass()而不是instanceof?


175

我正在使用Eclipse生成.equals().hashCode(),并且有一个标记为“使用'instanceof'比较类型”的选项。缺省是不选中此选项并用于.getClass()比较类型。有什么我.getClass()比我更喜欢的理由instanceof吗?

不使用instanceof

if (obj == null)
  return false;
if (getClass() != obj.getClass())
  return false;

使用instanceof

if (obj == null)
  return false;
if (!(obj instanceof MyClass))
  return false;

我通常会选中该instanceof选项,然后进入并删除“ if (obj == null)”检查。(这是多余的,因为空对象将始终失败instanceof。)是否有任何不好的主意?


1
x instanceof SomeClass如果x为,则表达式为false null。因此,第二种语法不需要null检查。
rds

5
@rds是的,紧接在代码片段后面的段落也是如此。它在代码段中,因为这是Eclipse生成的。
基普(Kip)

Answers:


100

如果你使用instanceof,让您equals实现final将保留方法的对称性合同:x.equals(y) == y.equals(x)。如果final看起来有限制,请仔细检查您的对象等效性概念,以确保您的首要实现完全维护Object该类所建立的契约。


当我读到上面的乔什·布洛(Josh bloch)报价时,这正是我想到的。+1 :)
Johannes Schaub-litb

13
绝对是关键的决定。必须应用对称性,instanceof使其很容易意外地不对称
Scott Stanchfield

我还要补充一点,“(如果和都final看似是限制性的)” equalshashCode则使用getClass()等式代替,instanceof以保持equals合同的对称性和可传递性要求。
Shadow Man

177

Josh Bloch支持您的方法:

我赞成该instanceof方法的原因是,当您使用该getClass方法时,存在一些限制,即对象只能与相同类,相同运行时类型的其他对象相等。如果扩展一个类并向其添加一些无害的方法,则检查子类的某个对象是否等于超类的对象,即使这些对象在所有重要方面都相等,您也会得到他们不平等的令人惊讶的答案。实际上,这违反了对Liskov替换原理的严格解释,并且可能导致非常令人惊讶的行为。在Java中,它特别重要,因为大多数集合(HashTable等)均基于equals方法。如果将父类的成员放在哈希表中作为键,然后使用子类实例进行查找,则找不到它们,因为它们不相等。

另请参阅此SO答案

有效的Java第3章也对此进行了介绍。


18
+1使用Java的每个人都应该阅读该书至少10次!
安德烈·

17
instanceof方法的问题在于,它破坏了Object.equals()的“对称”属性,因为如果x.getClass有可能x.equals(y)== true但y.equals(x)== false ()!= y.getClass()。除非绝对必要,否则我不希望破坏此属性(即,对托管或代理对象覆盖equals())。
Kevin Sitze 2013年

9
当且仅当基类定义子类对象之间的相等性意味着什么时,instanceof方法才是正确的。使用getClass不会违反LSP,因为LSP仅涉及可以使用现有实例完成的操作,而不涉及可以构造哪些类型的实例。返回的类getClass是对象实例的不可变属性。LSP并不意味着应该可以创建一个子类,在该子类中,该属性指示创建类之外的任何类。
2014年

66

安吉莉卡·兰格斯(Angelika Langers )平等的秘密通过一些常见和著名的例子,包括乔什·布洛赫(Josh Bloch)和芭芭拉·里斯科夫(Barbara Liskov)的讨论,经过漫长而详细的讨论,发现了其中的大多数问题。她还进入了instanceofVS getClass。引用一些

结论

剖析了equals()实现的四个任意选择的示例之后,我们得出的结论是什么?

首先:在equals()的实现中,有两种实质上不同的执行类型匹配检查的方法。一个类可以通过instanceof运算符允许在超类和子类对象之间进行混合类型比较,或者一个类可以通过getClass()测试将不同类型的对象视为非相等对象。上面的示例很好地说明了使用getClass()进行equals()的实现通常比使用instanceof的实现更健壮。

测试的instanceof仅对最终类是正确的,或者至少在父类中equals()方法是最终的。后者实质上意味着没有子类必须扩展超类的状态,而只能添加与对象的状态和行为无关的功能或字段,例如瞬态或静态字段。

另一方面,使用getClass()测试的实现始终符合equals()合同;他们是正确和强大的。但是,它们在语义上与使用instanceof test的实现有很大不同。使用getClass()的实现不允许将子类与超类对象进行比较,即使子类未添加任何字段并且甚至都不想覆盖equals()时也是如此。这样的“琐碎”类扩展例如是在为该“琐碎”目的而定义的子类中添加了调试打印方法。如果超类通过getClass()检查禁止进行混合类型比较,那么普通扩展将无法与其超类进行比较。这是否是一个问题,完全取决于类的语义和扩展的目的。


61

使用的原因getClass是为了确保equals合同的对称性。从equals的JavaDocs:

它是对称的:对于任何非空参考值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true。

通过使用instanceof,可能不对称。考虑示例:狗扩展动物。动物equalsinstanceof检查动物。狗狗equalsinstanceof检查狗狗。赋予Animal a和Dog d(其他字段相同):

a.equals(d) --> true
d.equals(a) --> false

这违反了对称属性。

为了严格遵守平等契约,必须确保对称性,因此类别必须相同。


8
这是该问题的第一个清晰明了的答案。一个代码示例值一千个单词。
约翰里德(Johnride)

我正在仔细研究所有其他冗长的答案,直到您省了一天。简单,简洁,优雅,是这个问题的最佳答案。

1
正如您提到的,有对称性要求。但是也有“传递性”的要求。如果a.equals(c)b.equals(c)a.equals(b)Dog.equalsreturn super.equals(object)!(object instanceof Dog)它是Dog实例时检查其他字段的天真的方法不会违反对称性,但会违反传递性)
Shadow Man

为了安全起见,您可以使用getClass()相等性检查,也可以使用instanceofmakeequalshashCodemethod进行检查final
Shadow Man

27

这是一场宗教辩论。两种方法都有其问题。

  • 使用instanceof,您将永远无法将重要成员添加到子类中。
  • 使用getClass会违反Liskov替换原则。

BlochEffective Java Second Edition中还有另一条相关的建议:

  • 项目17:设计和文件继承或禁止继承

1
getClass除非基类专门记录了一种手段,使使用该子手段的比较子类的实例相等,否则使用LSP都不会违反LSP。您看到什么违反LSP的行为?
supercat

也许应该将其改写为Use getClass,并且让子类型处于违反LSP的危险中。Bloch在有效Java中有一个示例-也在这里讨论。他的基本原理是,对于实施不添加状态的子类型的开发人员,这可能导致令人惊讶的行为。我明白你的意思-文档是关键。
McDowell 2014年

2
唯一的不同类的两个实例应该报告自己相等的唯一情况是,它们是否从定义了相等性含义的公共基类或接口继承[Java怀疑地IMHO应用于某些集合接口的哲学]。除非基类合同指出,getClass()除了所讨论的类可转换为基类的事实外,不应将其视为有意义,否则应将其返回getClass()值视为必须与实例必须相等才能匹配的其他任何属性一样。
supercat 2014年

1
@eyalzba如果子类将成员添加到equals契约中,则使用instanceof_会违反对称性平等的要求。使用getClass子类永远不能等于父类型。并不是说无论如何都应该覆盖平等,但这是折衷的选择。
McDowell 2015年

2
我认为“设计和记录继承或禁止继承”是非常重要的。我的理解是,只有在创建不可变值类型时才应覆盖equals,在这种情况下,应将类声明为final。由于没有继承,因此您可以根据需要自由实现equals。在允许继承的情况下,对象应按引用相等性进行比较。
TheSecretSquad

24

如果我错了,请纠正我,但是当您要确保您的实例不是所比较类的子类时,getClass()将很有用。如果您在这种情况下使用instanceof,可能会因为以下原因而无法得知:

class A { }

class B extends A { }

Object oA = new A();
Object oB = new B();

oA instanceof A => true
oA instanceof B => false
oB instanceof A => true // <================ HERE
oB instanceof B => true

oA.getClass().equals(A.class) => true
oA.getClass().equals(B.class) => false
oB.getClass().equals(A.class) => false // <===============HERE
oB.getClass().equals(B.class) => true

对我来说,这就是原因
karlihnos

5

如果您想确保只有该类将匹配,请使用getClass() ==。如果要匹配子类,则instanceof需要。

此外,instanceof将不会与null匹配,但可以安全地与null进行比较。因此,您不必进行空检查。

if ( ! (obj instanceof MyClass) ) { return false; }

关于您的第二点:他已经在问题中提到了这一点。“我通常检查instanceof选项,然后删除'if(obj == null)'检查。”
迈克尔·迈尔斯

5

这取决于您是否考虑给定类的子类是否等于其父类。

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

在这里我将使用'instanceof',因为我希望将姓氏与FamilyName进行比较

class Organism
{
}

class Gorilla extends Organism
{
}

在这里,我将使用“ getClass”,因为该类已经说过这两个实例并不等效。


3

instanceof适用于同一类或其子类的实例

您可以使用它来测试对象是实现特定接口的类的实例,子类的实例还是类的实例。

ArryaList和RoleList都是List的instanceof

getClass()== o.getClass()仅在两个对象(this和o)完全属于同一类时才为true。

因此,根据需要进行比较,您可以使用其中之一。

如果您的逻辑是:“仅当两个对象属于同一类时,一个对象才等于其他对象”,您应该使用“等于”,我认为这是大多数情况。


3

两种方法都有其问题。

如果子类更改了标识,则需要比较其实际类。否则,您将违反对称属性。例如Person,即使不同类型的s具有相同的名称,也不应视为等效。

但是,某些子类不会更改标识,而需要使用instanceof。例如,如果我们有一堆不可变的Shape对象,那么Rectangle长和宽为1的a应该等于unit Square

实际上,我认为前一种情况更可能是正确的。通常,子类化是您身份的基本组成部分,与父类完全一样,除了您可以做一件小事并不能使您平等。


-1

实际上是instanceof检查对象是否属于某个层次结构的地方。例如:Car对象属于Vehical类。因此,“ Vehical的new Car()实例”返回true。尽管Car对象属于Vehical类,但“ new Car()。getClass()。equals(Vehical.class)”返回false,但它被归类为单独的类型。

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.