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

Java 重寫 equals() 與 hashCode() 完整指南:基於 Effective Java 最佳實踐

     編輯:Java 相關知識

何時必須重寫 equals()?

當類別具有「邏輯相等」語義(而非單純的物件身份比較)時。典型場景:值物件、DTO、實體類、集合元素、Map Key。

equals() 實作五步法

  1. instanceof 檢查型別:if (!(o instanceof MyClass)) return false;
  2. 轉型並逐欄位比較:
欄位類型比較方式
基本型別(非 float/double)==
物件引用遞歸呼叫 equals()
floatFloat.floatToIntBits(f1) == Float.floatToIntBits(f2)
doubleDouble.doubleToLongBits(d1) == Double.doubleToLongBits(d2)
陣列Arrays.equals(arr1, arr2)
  1. 滿足契約:自反性、對稱性、傳遞性、一致性、非空性。

為何重寫 equals() 必須重寫 hashCode()?

Java 規範:a.equals(b) == true ⇒ a.hashCode() == b.hashCode()。若僅重寫 equals(),物件放入 HashMap/HashSet 會導致「找不到已放入的元素」等嚴重 Bug。

hashCode() 經典演算法(Josh Bloch 版)

public int hashCode() {
    int result = 17; // 非零質數種子
    result = 31 * result + field1Hash; // 31 為質數,乘法散列
    result = 31 * result + field2Hash;
    // ... 所有參與 equals 比較的欄位
    return result;
}

各型別哈希計算對照表:

型別計算方式
boolean(f ? 0 : 1)
byte/char/short/int(int) f
long(int) (f ^ (f >>> 32))
floatFloat.floatToIntBits(f)
doubleDouble.doubleToLongBits(d) 再按 long 處理
物件引用f == null ? 0 : f.hashCode()
陣列逐元素遞歸 hashCode() 後合併

完整範例:涵蓋所有型別的 Unit 類

public class Unit {
    private short ashort;
    private char achar;
    private byte abyte;
    private boolean abool;
    private long along;
    private float afloat;
    private double adouble;
    private Unit aObject;
    private int[] ints;
    private Unit[] units;

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Unit)) return false;
        Unit u = (Unit) o;
        return u.ashort == ashort
            && u.achar == achar
            && u.abyte == abyte
            && u.abool == abool
            && u.along == along
            && Float.floatToIntBits(u.afloat) == Float.floatToIntBits(afloat)
            && Double.doubleToLongBits(u.adouble) == Double.doubleToLongBits(adouble)
            && (aObject == null ? u.aObject == null : aObject.equals(u.aObject))
            && Arrays.equals(ints, u.ints)
            && Arrays.equals(units, u.units);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + (int) ashort;
        result = 31 * result + (int) achar;
        result = 31 * result + (int) abyte;
        result = 31 * result + (abool ? 0 : 1);
        result = 31 * result + (int) (along ^ (along >>> 32));
        result = 31 * result + Float.floatToIntBits(afloat);
        long tmp = Double.doubleToLongBits(adouble);
        result = 31 * result + (int) (tmp ^ (tmp >>> 32));
        result = 31 * result + (aObject == null ? 0 : aObject.hashCode());
        result = 31 * result + Arrays.hashCode(ints);
        result = 31 * result + Arrays.hashCode(units); // Java 7+ Arrays.hashCode 支援物件陣列
        return result;
    }
}

現代開發工具輔助

  • IDE 自動生成:IntelliJ Alt+Insert → equals() and hashCode()、Eclipse Source → Generate hashCode() and equals()。
  • Lombok:@EqualsAndHashCode 一行搞定,支援 callSuper、onlyExplicitlyIncluded 等進階配置。
  • 單元測試驗證:使用 EqualsVerifier 庫自動驗證契約合規性。

總結

equals() 與 hashCode() 是一對「雙胞胎」,缺一不可。遵循 Effective Java 經典演算法,善用 IDE/Lombok 工具,配合 EqualsVerifier 測試,即可寫出健壯、高效的值物件類別。

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