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

Effective Java 條目 11:覆蓋 equals 時必須覆蓋 hashCode —— 緩存哈希碼的高性能實現

     編輯:Java 綜合教學

核心法則

「在覆蓋 equals 時,必須覆蓋 hashCode。」 —— 《Effective Java》第 3 版,條目 11

違反此法則會導致該類無法在 HashMap、HashSet 等基於哈希的集合中正常工作:邏輯上相等的對象可能被放入不同桶,導致 get 返回 null、contains 返回 false。

反面教材:PhoneNumber 未覆蓋 hashCode

public final class PhoneNumber {
    private final short areaCode, prefix, lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = (short) areaCode;
        this.prefix = (short) prefix;
        this.lineNumber = (short) lineNumber;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof PhoneNumber)) return false;
        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
    }

    // 故意不覆蓋 hashCode → 繼承 Object.hashCode()(基於對象地址)
}

public static void main(String[] args) {
    Map m = new HashMap<>();
    m.put(new PhoneNumber(707, 867, 5309), "Jenny");
    System.out.println(m.get(new PhoneNumber(707, 867, 5309))); // null!

標準 hashCode 實現模板

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + areaCode;
    result = 31 * result + prefix;
    result = 31 * result + lineNumber;
    return result;
}
  • 17、31:經典質數,31 * i 可優化為 (i << 5) - i。
  • 所有參與 equals 的字段必須參與 hashCode 計算。
  • 基本類型直接混入;引用類型調用其 hashCode()(處理 null 返回 0);數組用 Arrays.hashCode;boolean用 (f ? 1 : 0)。

進階優化:緩存哈希碼(適用於不可變對象、計算開銷大)

public final class PhoneNumber {
    // ... 字段、構造器、equals 同前 ...

    // 緩存哈希碼,volatile 保證發布安全性
    private volatile int hashCode = 0; // 0 表示未計算

    @Override
    public int hashCode() {
        int result = hashCode;
        if (result == 0) { // 首次計算
            result = 17;
            result = 31 * result + areaCode;
            result = 31 * result + prefix;
            result = 31 * result + lineNumber;
            hashCode = result; // 寫入緩存
        }
        return result;
    }
}

關鍵點:

  1. volatile 確保多線程環境下,一個線程計算完成後,其他線程可見最新值(雙重檢查鎖模式的簡化版)。
  2. 僅適用於**不可變對象**;若字段可變,緩存會導致哈希碼過期,破壞契約。
  3. 對於 String、BigInteger 等 JDK 類,內部已採用此優化。

自動生成工具推薦

  • IDE:IntelliJ IDEA Alt+Insert → equals() and hashCode()
  • Lombok:@EqualsAndHashCode(可指定 onlyExplicitlyIncluded = true 手動控制字段)
  • AutoValue / Immutables:編譯期生成不可變值類,自動實現 equals/hashCode/toString。

常見誤區

❌ 只覆蓋 equals,不覆蓋 hashCode
→ 集合失效,難以排查。
❌ hashCode 計算包含可變字段
→ 對象放入集合後修改字段,導致找不到。
❌ hashCode 為常量(如 return 42;)
→ 合法但退化為鏈表,性能極差。
✅ 正確:equals 用到的所有字段、全部不可變、全部參與 hashCode
→ 集合行為正確、性能優異。

總結

equals 與 hashCode 是一對「雙生契約」。覆蓋其一必覆蓋另一,且**依賴字段集合完全一致、全部不可變**。對於高頻使用的不可變值類,添加 volatile 緩存哈希碼是極低成本的性能優化。下次重寫 equals 時,請同時按 Alt+Insert 生成 hashCode,並檢查字段不可變性——這是專業 Java 開發者的基本功。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved