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

Java 核心基石:equals() 與 hashCode() 契約深度解析與最佳實踐

     編輯:Java 綜合教學

equals() 與 hashCode():HashMap 正確運行的雙重保障

在 Java 集合框架中,正確重寫 `equals()` 和 `hashCode()` 是將自定義對象作為 `HashMap` Key、存入 `HashSet` 的前提。本文通過一個經典失敗案例,深度解析兩者契約、底層原理及正確實現姿勢。

一、 經典失敗案例:綠蘋果消失之謎

class Apple {
    private String color;
    public Apple(String color) { this.color = color; }
    // 重寫 equals,未重寫 hashCode
    public boolean equals(Object obj) {
        if (!(obj instanceof Apple)) return false;
        if (obj == this) return true;
        return this.color.equals(((Apple) obj).color);
    }
}

public static void main(String[] args) {
    Map<Apple, Integer> map = new HashMap<>();
    Apple a1 = new Apple("green");
    map.put(a1, 10); // 存入
    System.out.println(map.get(new Apple("green"))); // 輸出 null!為什麼?
}

現象:明明存入了 "green" 蘋果,用同樣顏色的新對象取值卻返回 `null`。調試發現 Map 內確實有該對象。

二、 根因分析:hashCode() 缺失導致「找不到桶」

  1. HashMap 存儲結構:數組 + 鏈表/紅黑樹。Key 存放位置由 `hashCode()` 決定數組下標 (桶)。
  2. 查找流程:
    1. 計算 Key 的 `hashCode()` → 定位數組下標。
    2. 遍歷該桶下的鏈表,調用 `equals()` 比較找到目標 Entry。
  3. 默認 hashCode():`Object` 默認實現通常基於對象內存地址,不同實例 hashCode 不同。
  4. 案例中:`put(a1)` 使用 `a1.hashCode()` 存入桶 A;`get(new Apple("green"))` 使用新對象的 `hashCode()` 定位到桶 B → 桶 B 為空 → 直接返回 `null`,根本不會走到 `equals()` 比較。

三、 核心契約

1. 如果兩個對象 equals() 返回 true,則它們的 hashCode() 必須相等。
2. 如果兩個對象 hashCode() 相等,它們 equals() 不一定為 true (允許哈希碰撞)。

違反契約 1 會導致「等價對象落入不同桶」,集合無法正常工作。

四、 正確重寫姿勢

方案 1:使用 Objects 工具類 (Java 7+ 推薦)

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Apple apple = (Apple) o;
    return Objects.equals(color, apple.color);
}

@Override
public int hashCode() {
    return Objects.hash(color); // 自動處理 null,生成合理哈希
}

方案 2:手動實現 (教學演示)

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + (color == null ? 0 : color.hashCode());
    return result;
}

五、 設計原理:為什麼要這樣設計?

哈希分桶是為了 O(1) 平均查找性能。將對象盡量均勻分散到不同桶 (像貨物分倉庫),避免所有對象擠在一個桶退化為 O(n) 線性查找。`hashCode()` 決定「哪個倉庫」,`equals()` 決定「是不是目標貨物」。

六、 總結清單

  • ✅ 重寫 `equals()` 必須 重寫 `hashCode()`。
  • ✅ 兩者使用的屬性集合必須一致 (通常為唯一業務標識字段)。
  • ✅ `hashCode()` 計算應穩定 (不可變對象最佳),避免對象入集合後屬性變更導致找不到。
  • ✅ 優先使用 `Objects.equals()` / `Objects.hash()` 或 IDE 自動生成 / Lombok `@EqualsAndHashCode`。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved