首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java equals() 方法深度總結:契約、陷阱與最佳實踐

     編輯:Java 相關知識

核心契約 (Java SE Spec)

equals() 必須滿足等價關係五大公理:

  1. 自反性:x.equals(x) == true
  2. 對稱性:x.equals(y) == y.equals(x)
  3. 傳遞性:x.equals(y) && y.equals(z) → x.equals(z)
  4. 一致性:多次呼叫結果不變,除非比較依據的欄位被修改。
  5. 非空性:x.equals(null) == false

違反契約後果:導致 HashSet、HashMap、ArrayList.contains() 等集合行為錯誤 (如無法找到已放入的元素、重複元素)。

Object.equals() 預設行為


public boolean equals(Object obj) {
    return (this == obj); // 比較記憶體位址 (身分相等)
}

所有類別預設繼承此行為,**值物件 (Value Object) 必須覆寫** 比較內容。

標準覆寫範本 (Java 7+ Objects 工具類)


@Override
public boolean equals(Object obj) {
    if (this == obj) return true;                    // 同一物件快速返回
    if (obj == null || getClass() != obj.getClass()) return null; // 型別檢查
    MyClass other = (MyClass) obj;
    return Objects.equals(field1, other.field1) &&
           Objects.equals(field2, other.field2) &&
           primitiveField == other.primitiveField;
}

關鍵決策:getClass() vs instanceof

策略行為優點缺點適用場景
getClass()嚴格型別相等滿足對稱性、傳遞性;簡單安全子類別無法與父類別實例相等預設首選、final 類別、值物件、不可變物件
instanceof允許子類別比較多型友善極易違反對稱性/傳遞性 (見下例)極少數需跨類別層級比較的框架代碼 (如 JPA 實體)

instanceof 破壞對稱性經典反例


class Person {
    String name;
    // 使用 instanceof
    public boolean equals(Object o) {
        if (o instanceof Person) return name.equals(((Person)o).name);
        return false;
    }
}

class Employee extends Person {
    int id;
    // 覆寫加入 id 比較
    public boolean equals(Object o) {
        if (o instanceof Employee) return super.equals(o) && id == ((Employee)o).id;
        return false;
    }
}

Person p = new Person("John");
Employee e1 = new Employee("John", 1);
Employee e2 = new Employee("John", 2);

p.equals(e1); // true  (Person 用 instanceof 比較 name)
p.equals(e2); // true  (Person 用 instanceof 比較 name)
e1.equals(e2); // false (Employee 比較 id)
// 違反傳遞性:p=e1 且 p=e2 但 e1!=e2

結論:除非極度確定需求,**永遠使用 getClass()** 或 obj.getClass() == this.getClass()。

hashCode() 必須同步覆寫


@Override
public int hashCode() {
    return Objects.hash(field1, field2, primitiveField);
}

鐵律:a.equals(b) → a.hashCode() == b.hashCode()。違反會導致 HashMap/HashSet 失效。

各類型比較策略速查表

為什麼 float/double 需要特殊處理?

  • Float.NaN == Float.NaN 是 false,但 Float.equals() 視為相等。
  • +0.0f == -0.0f 是 true,但 Float.equals() 視為不等 (符號不同)。
  • floatToIntBits / doubleToLongBits 將位元模式轉為整數比較,正確處理上述邊界情況。

現代工具自動生成

  • IDE (IntelliJ IDEA / Eclipse) → Generate → equals() and hashCode()。
  • Lombok:@EqualsAndHashCode 註解自動生成,支援 callSuper、onlyExplicitlyIncluded 等精細控制。
  • Records (Java 14+):record Person(String name, int age) {} 自動生成正確的 equals/hashCode/toString,**首選不可變資料載體**。

常見錯誤清單

  1. 參數型別寫成具體類別 equals(MyClass other) → 重載而非覆寫,@Override 會報錯。
  2. 忘記 null 檢查 → NPE。
  3. 使用 instanceof 導致對稱性/傳遞性破壞。
  4. 只覆寫 equals 不覆寫 hashCode。
  5. 比較可變欄位 → 物件放入 HashSet 後修改欄位導致找不到。
  6. 在 equals 中執行昂貴運算 / I/O / 鎖定。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
類型比較方式
基本型別 (int, long 等)==
floatFloat.compare(f1, f2) == 0 或 Float.floatToIntBits(f1) == Float.floatToIntBits(f2)
doubleDouble.compare(d1, d2) == 0 或 Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2)
物件參考Objects.equals(o1, o2) (內建 null 安全)
陣列Arrays.equals(arr1, arr2) / Arrays.deepEquals
列舉== 或 equals() (單例保證)