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() 並違反關鍵假設。