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

Java hashCode 與 equals 方法深度解析:核心原理與最佳實踐

     編輯:Java 程式設計入門

前言:為什麼要關心 hashCode 與 equals?

在 Java 開發中,hashCode() 和 equals() 是 Object 類中兩個最基礎卻又最容易被忽視的方法。既然 Object 是所有類的父類,這意味著每個 Java 物件都擁有這兩個方法。它們在集合框架(特別是 HashMap、HashSet 等基於哈希表的容器)中扮演著關鍵角色:決定物件的唯一性與儲存位置。

若不正確重寫這兩個方法,可能導致邏輯錯誤、效能低落,甚至資料不一致。本文將從原理、規範、實戰案例三個維度,帶你徹底掌握它們的核心機制。

一、核心原理:JVM 如何判斷物件「相同」?

1. equals() 的預設行為

在 Object 類中,equals() 的實現極其簡單:

public boolean equals(Object obj) {
    return (this == obj);
}

這代表預設比較的是「記憶體位址」(即引用是否指向同一個物件)。但許多核心類(如 String、Integer、Date 等)已重寫此方法,改為比較「內容值」。

2. hashCode() 的本質

hashCode() 是一個 native 方法,其預設實現通常基於物件的記憶體位址轉換而來。以 String 為例,其演算法為:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

關鍵點:hashCode 返回的不一定是真實的記憶體位址,而是一個經過演算法計算的整數「指紋」。

3. 哈希表(Hash Table)的運作邏輯

當向 HashMap 或 HashSet 新增元素時:

  1. 先呼叫 hashCode() 計算雜湊值,定位到桶位(bucket)。
  2. 若該位置為空,直接存入。
  3. 若已有元素,則呼叫 equals() 逐一比對,確認是否真正「相同」。

這機制極大降低了比較次數,從 O(n) 降至接近 O(1)。

二、鐵律規範:重寫時必須遵守的契約

Java 語言規範(JLS)強制要求:

  • 規則一: 若 a.equals(b) == true,則 a.hashCode() == b.hashCode() 必須成立。
  • 規則二: 若 a.equals(b) == false,hashCode() 可以相同(哈希衝突),也可以不同。

違反規則一雖不會編譯報錯,但會導致基於哈希的集合失效(如 HashSet 存入重複物件、HashMap 找不到鍵值)。

推論總結:

條件hashCode 必相等?equals 必相等?
equals 為 true是是
equals 為 false不一定否
hashCode 相等—不一定(衝突)
hashCode 不等—一定不等

三、實戰驗證:一個經典的測試案例

class A {
    @Override
    public boolean equals(Object obj) {
        System.out.println("判斷 equals");
        return false; // 永遠不相等
    }
    @Override
    public int hashCode() {
        System.out.println("判斷 hashCode");
        return 1; // 永遠返回 1,製造衝突
    }
}

public class Test {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put(new A(), new Object());
        map.put(new A(), new Object());
        System.out.println(map.size()); // 輸出 2
    }
}

輸出分析:

判斷 hashCode  // 第 1 次 put
判斷 hashCode  // 第 2 次 put,發現 hash 相同
判斷 equals    // 必須進一步比對
2

雖然 hashCode 相同,但 equals 返回 false,因此視為不同鍵,size() 為 2。這證實了「先比 hash,再比 equals」的流程。

四、equals() 重寫的五大原則

除了與 hashCode 的契約,equals() 本身必須滿足:

  1. 對稱性: x.equals(y) 為 true → y.equals(x) 也為 true。
  2. 自反性: x.equals(x) 必為 true。
  3. 傳遞性: x.equals(y) 且 y.equals(z) 為 true → x.equals(z) 為 true。
  4. 一致性: 多次調用結果不變(前提是物件內容未變)。
  5. 非空性: x.equals(null) 永遠返回 false;與不同類型物件比較也返回 false。

違反任一原則,都可能在集合操作中產生不可預期的 Bug。

五、最佳實踐建議

  • 總是成對重寫: 覆寫 equals() 時,務必覆寫 hashCode()。
  • 使用相同欄位: 兩個方法應基於同一組「業務關鍵欄位」計算(如 ID、唯一業務鍵)。
  • 善用工具: IDE(IntelliJ IDEA、Eclipse)可自動生成標準實現;或使用 Objects.hash()、Objects.equals() 簡化代碼。
  • 不可變物件: 若物件不可變,可快取 hashCode 提升效能(如 String)。

結語

hashCode() 與 equals() 是 Java 物件身份識別的基石。理解其底層邏輯、嚴守規範契約、並在實戰中正確實現,是寫出健壯、高效 Java 代碼的必修課。下次當你定義一個會放入 HashSet 或作為 HashMap 鍵的類時,請務必花幾分鐘正確實現這兩個方法——這將為你省去無數調試的夜晚。

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