187

我正在使用 Eclipse 生成.equals()and .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。)有什么理由是个坏主意吗?

4

11 回答 11

185

乔什·布洛赫(Josh Bloch)赞成您的方法:

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

另请参阅此 SO 答案

Effective Java第 3 章也涵盖了这一点。

于 2009-02-27T20:21:55.507 回答
107

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

于 2009-02-27T21:11:51.907 回答
68

使用的原因getClass是为了保证equals合约的对称性。来自 equals 的 JavaDocs:

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

通过使用instanceof,可能不是对称的。考虑这个例子:Dog 扩展 Animal。Animal's对 Animalequals进行instanceof检查。Dog's对 Dogequals进行instanceof检查。给出 Animal a和 Dog d(其他字段相同):

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

这违反了对称性。

要严格遵循equal的契约,必须保证对称性,因此类需要相同。

于 2009-02-27T21:34:21.120 回答
68

Angelika Langers Secrets of equals对一些常见和知名的例子进行了长时间而详细的讨论,包括 Josh Bloch 和 Barbara Liskov 的例子,在其中发现了一些问题。她也进入了instanceofvs getClass。引用其中的一些内容

结论

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

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

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

另一方面,使用 getClass() 测试的实现总是遵守 equals() 契约;它们是正确且稳健的。然而,它们在语义上与使用 instanceof 测试的实现非常不同。使用 getClass() 的实现不允许将子类与超类对象进行比较,即使子类不添加任何字段并且甚至不想覆盖 equals() 也是如此。例如,这种“微不足道”的类扩展将是在为这个“微不足道”目的而定义的子类中添加调试打印方法。如果超类通过 getClass() 检查禁止混合类型比较,那么微不足道的扩展将无法与其超类进行比较。这是否是一个问题完全取决于类的语义和扩展的目的。

于 2009-02-27T21:52:39.080 回答
27

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

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

Bloch在Effective Java Second Edition中有另一条相关建议:

  • 第 17 项:继承或禁止继承的设计和文件
于 2009-02-27T22:15:14.000 回答
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
于 2011-10-26T05:03:34.267 回答
5

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

此外,instanceof 不会与 null 匹配,但可以安全地与 null 进行比较。所以你不必对它进行空检查。

if ( ! (obj instanceof MyClass) ) { return false; }
于 2009-02-27T20:24:48.323 回答
5

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

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

在这里我会使用“instanceof”,因为我希望将 LastName 与 FamilyName 进行比较

class Organism
{
}

class Gorilla extends Organism
{
}

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

于 2009-02-27T20:25:19.757 回答
3

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

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

ArryaList 和 RoleList 都是instanceof List

尽管

getClass() == o.getClass()仅当两个对象( this 和 o )都属于完全相同的类时才为真。

因此,根据您需要比较的内容,您可以使用其中一种。

如果您的逻辑是:“只有当它们都是同一个类时,一个对象才等于另一个对象”,那么您应该选择“等于”,我认为这是大多数情况。

于 2009-02-27T20:33:29.707 回答
3

两种方法都有其问题。

如果子类改变了身份,那么你需要比较它们的实际类。否则,您违反了对称属性。例如,不同类型的Persons 不应该被认为是等价的,即使它们具有相同的名称。

但是,一些子类不会改变身份,这些需要使用instanceof. 例如,如果我们有一堆不可变Shape对象,那么Rectangle长度和宽度为 1 的 a 应该等于 unit Square

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

于 2009-02-27T22:06:10.420 回答
-1

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

于 2015-12-01T13:27:32.197 回答