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

Java hashCode 與 equals 核心契約深度解析

     編輯:Java 相關知識

為什麼必須同時重寫 hashCode 與 equals?

在 Java 集合框架中,HashMap、HashSet 等依賴雜湊表儲存資料。JVM 先比較 hashCode() 定位桶位,再呼叫 equals() 確認物件相等。若違反契約,會導致集合行為異常。

Object 類別預設實作

// Object.equals():比較記憶體位址
public boolean equals(Object obj) {
    return (this == obj);
}

// Object.hashCode():native 方法,通常基於記憶體位址
public native int hashCode();

String、Integer 等包裝類別已覆寫 equals() 比較內容值,hashCode() 也依內容計算(如 String 使用 31 為基數的多項式雜湊)。

核心契約三條規則

  1. 一致性:若 a.equals(b) == true,則 a.hashCode() == b.hashCode() 必須成立。
  2. 非對稱性:equals() 回傳 false 時,hashCode() 可以相同(雜湊碰撞)。
  3. 穩定性:同一物件多次呼叫 hashCode() 應回傳相同值(前提是 equals 比較欄位未變)。

違反契約的後果

class Broken {
    @Override public boolean equals(Object o) { return true; }
    @Override public int hashCode() { return new Random().nextInt(); } // 每次不同
}

Set<Broken> set = new HashSet<>();
set.add(new Broken());
set.add(new Broken());
System.out.println(set.size()); // 可能輸出 2,甚至更多,違反 Set 不重複特性

實戰測試:HashMap 存取流程

class A {
    @Override public boolean equals(Object obj) {
        System.out.println("呼叫 equals");
        return false; // 永遠不相等
    }
    @Override public int hashCode() {
        System.out.println("呼叫 hashCode");
        return 1; // 固定雜湊碼
    }
}

public class Test {
    public static void main(String[] args) {
        Map<A, Object> map = new HashMap<>();
        map.put(new A(), new Object()); // 呼叫 hashCode
        map.put(new A(), new Object()); // 呼叫 hashCode → 發現碰撞 → 呼叫 equals
        System.out.println("Map size: " + map.size()); // 輸出 2
    }
}

輸出分析:
第一次 put 僅呼叫 hashCode();
第二次 put 發現相同雜湊碼,進而呼叫 equals() 比較,因回傳 false 視為不同鍵值,故 size 為 2。

equals() 必須遵守的五大原則

  • 反射性:x.equals(x) == true
  • 對稱性:x.equals(y) == y.equals(x)
  • 傳遞性:x.equals(y) && y.equals(z) → x.equals(z)
  • 一致性:多次呼叫結果不變(前提欄位未修改)
  • 非空性:x.equals(null) == false

最佳實踐建議

  1. 使用 IDE 自動產生 equals() 與 hashCode()(基於相同欄位)。
  2. 不可變物件可快取 hashCode() 提升效能。
  3. 若類別不可變且頻繁作為 Map 鍵,考慮使用 @Value (Lombok) 或 record (Java 16+)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved