equals() 方法

TL; DR

== 测试参考相等性(它们是否是同一个对象

.equals() 测试价值相等(它们在逻辑上是否 相等

equals() 是一种用于比较两个对象是否相等的方法。当且仅当两个引用都指向同一个实例时,Object 类中 equals() 方法的默认实现返回 true。因此它与 == 的比较表现相同。

public class Foo {
    int field1, field2;
    String field3;

    public Foo(int i, int j, String k) {
        field1 = i;
        field2 = j;
        field3 = k;
    }

    public static void main(String[] args) {
        Foo foo1 = new Foo(0, 0, "bar");
        Foo foo2 = new Foo(0, 0, "bar");

        System.out.println(foo1.equals(foo2)); // prints false
    }
}

尽管 foo1foo2 是使用相同的字段创建的,但它们指向内存中的两个不同对象。因此,默认的 equals() 实现计算为 false

为了比较对象的内容是否相等,必须覆盖 equals()

public class Foo {
    int field1, field2;
    String field3;

    public Foo(int i, int j, String k) {
        field1 = i;
        field2 = j;
        field3 = k;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        Foo f = (Foo) obj;
        return field1 == f.field1 &&
               field2 == f.field2 &&
               (field3 == null ? f.field3 == null : field3.equals(f.field3));
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = 31 * hash + this.field1;
        hash = 31 * hash + this.field2;
        hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
        return hash;
    }

    public static void main(String[] args) {
        Foo foo1 = new Foo(0, 0, "bar");
        Foo foo2 = new Foo(0, 0, "bar");

        System.out.println(foo1.equals(foo2)); // prints true
    }
}

这里重写的 equals() 方法决定了如果它们的字段相同则对象是相等的。

请注意,hashCode() 方法也被覆盖了。该方法的契约表明,当两个对象相等时,它们的哈希值也必须相同。这就是为什么一个人必须几乎总是一起覆盖 hashCode()equals()

特别注意 equals 方法的参数类型。它是 Object obj,而不是 Foo obj。如果你把后者放在你的方法中,那不是 equals 方法的覆盖。

在编写自己的类时,在覆盖 equals()hashCode() 时必须编写类似的逻辑。大多数 IDE 都可以自动为你生成。

可以在 String 类中找到 equals() 实现的示例,该类是核心 Java API 的一部分。String 类不是比较指针,而是比较 String 的内容。

Version >= Java SE 7

Java 1.7 引入了 java.util.Objects 类,它提供了一种方便的方法 equals,它比较了两个潜在的 null 引用,因此它可以用来简化 equals 方法的实现。

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null || getClass() != obj.getClass()) {
        return false;
    }

    Foo f = (Foo) obj;
    return field1 == f.field1 && field2 == f.field2 && Objects.equals(field3, f.field3);
}

类比较

由于 equals 方法可以针对任何对象运行,因此该方法经常做的第一件事(检查 null 之后)是检查被比较对象的类是否与当前类匹配。

@Override
public boolean equals(Object obj) {
    //...check for null
    if (getClass() != obj.getClass()) {
        return false;
    }
    //...compare fields
}

这通常通过比较类对象来完成。但是,在一些可能不明显的特殊情况下,这可能会失败。例如,一些框架生成类的动态代理,这些动态代理实际上是一个不同的类。以下是使用 JPA 的示例。

Foo detachedInstance = ...
Foo mergedInstance = entityManager.merge(detachedInstance);
if (mergedInstance.equals(detachedInstance)) {
    //Can never get here if equality is tested with getClass()
    //as mergedInstance is a proxy (subclass) of Foo
}

解决该限制的一种机制是使用 instanceof 比较类

@Override
public final boolean equals(Object obj) {
    if (!(obj instanceof Foo)) {
        return false;
    }
    //...compare fields
}

但是,使用 instanceof 时必须避免一些陷阱。由于 Foo 可能有其他子类,而那些子类可能会覆盖 equals(),你可能会遇到 Foo 等于 FooSubclassFooSubclass 不等于 Foo 的情况。

Foo foo = new Foo(7);
FooSubclass fooSubclass = new FooSubclass(7, false);
foo.equals(fooSubclass) //true
fooSubclass.equals(foo) //false

这违反了对称性和传递性的特性,因此是 equals() 方法的无效实现。因此,当使用 instanceof 时,一个好的做法是制作 equals() 方法 final(如上例所示)。这将确保没有子类覆盖 equals() 并违反关键假设。