equals與hashCode關係梳理

Mysticbinary發表於2024-08-26

目錄
  • equals用法
  • hashCode用法
  • 總結
  • 為什麼一個類中需要兩個比較方法
  • 為什麼重寫 equals 方法時必須同時重寫 hashCode 方法?
  • Reference


這個並不是一個通用性程式設計問題,只屬於在Java領域內專有問題。

要做好心理準備,這是一個複雜類的問題,要解答這個問題,需要梳理清楚兩個函式和其它類之間的關係,並且它們之間的關係有點交織。

equals用法

在 Object 類中包含了 equals() 方法:

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

說明:

  • == 用於比較變數 所對應的記憶體中所儲存的數值 是否相同,要比較 兩個基本型別的資料(注意是基本型別)兩個引用變數 是否相等。

hashCode用法

在 Object 類中還包含了 hashCode() 方法:

public native int hashCode();

請回答,為什麼 Object 類需要一個 hashCode() 方法呢?

在 Java 中,hashCode() 方法的主要作用就是為了配合雜湊表使用的。

雜湊表(Hash Table),也叫雜湊表,是一種可以透過關鍵碼值(key-value)直接訪問的資料結構,它最大的特點就是可以快速實現查詢、插入和刪除。其中用到的演算法叫做雜湊,就是把任意長度的輸入,變換成固定長度的輸出,該輸出就是雜湊值。像 MD5、SHA1 都用的是雜湊演算法。

像 Java 中的 HashSet、Hashtable、HashMap 都是基於雜湊表的具體實現。其中的 HashMap 就是最典型的代表。

大家想一下,如果沒有雜湊表,但又需要這樣一個資料結構,它裡面存放的資料是不允許重複的,它是怎麼實現的?

  • 使用 equals() 方法進行逐個比較?
    這種方案當然是可行的。但如果資料量特別特別大,採用 equals() 方法進行逐個對比的效率肯定很低很低,總結:能解決,但效率不高。
  • 最好的解決方案就是雜湊表。總結:不光能解決,還效率高。

案例說明:
拿 HashMap 來說吧,當我們要在它裡面新增物件時,先呼叫這個物件的 hashCode() 方法,得到對應的雜湊值,然後將雜湊值和物件一起放到 HashMap 中。當我們要再新增一個新的物件時:

  1. 獲取物件的雜湊值;
  2. 和之前已經存在的雜湊值進行比較,如果不相等,直接存進去;
  3. 如果有相等的,再呼叫 equals() 方法進行物件之間的比較,如果相等,不存了;
  4. 如果不等,說明雜湊衝突了,增加一個連結串列,存放新的物件;
  5. 如果連結串列的長度大於 8,轉為紅黑樹來處理。

就這麼一套下來,呼叫 equals() 方法的頻率就大大降低了。也就是說,只要雜湊演算法足夠的高效,把發生雜湊衝突的頻率降到最低,雜湊表的效率就特別的高。

總結

== 用於比較變數所對應的記憶體中所儲存的數值是否相同,要比較兩個基本型別的資料(注意是基本型別)或兩個 引用變數是否相等,只能用==運算子。

equals 比較的是值和地址,如果沒有重寫equals方法,其作用與==相同;
在String類中,重寫了equals方法,比較的是是否相等;

hashCode用於雜湊資料結構中的hash值計算

equals兩個物件相等,那hashcode一定相等,hashcode相等,不一定是同一個物件(hash衝突現象);
hashCode 一般與 equals 一起使用,兩個物件作「相等」比較時,因判斷 hashCode 是判斷 equals 的先決條件.

為什麼一個類中需要兩個比較方法

因為重寫的 equals() 裡一般比較的比較全面比較複雜,這樣效率就比較低,而利用hashCode()進行對比,則只要生成一個 hash 值進行比較就可以了,效率很高,那麼 hashCode() 既然效率這麼高為什麼還要 equals() 呢?

  • 因為 hashCode() 並不是完全可靠,有時候不同的物件他們生成的 hashcode 也會一樣(hash衝突),所以 hashCode()只能說是大部分時候可靠,並不是絕對可靠。

  • equals() 相等的兩個物件他們的 hashCode() 肯定相等,也就是用 equals() 對比是絕對可靠的。

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

可以先看看Java這B 給出的一些建議,就是事前就規定好了...

public class Object {

    /**
     * Returns a hash code value for the object. This method is
     * supported for the benefit of hash tables such as those provided by
     * `java.util.HashMap`.
     *
     * The general contract of `hashCode` is:
     *
     * a) Whenever it is invoked on the same object more than once during
     *    an execution of a Java application, the `hashCode` method must
     *    consistently return the same integer, provided no information
     *    used in `equals` comparisons on the object is modified.
     *    This integer need not remain consistent from one execution of an
     *    application to another execution of the same application.
     *
     * b) If two objects are equal according to the `equals(Object)` method,
     *    then calling the `hashCode` method on each of the two objects must
     *    produce the same integer result.
     *
     * c) It is not required that if two objects are unequal according to the
     *    `equals(Object)` method, then calling the `hashCode` method on each of
     *    the two objects must produce distinct integer results.
     *    However, the programmer should be aware that producing distinct integer
     *    results for unequal objects may improve the performance of hash tables.
     */
    @IntrinsicCandidate
    public native int hashCode();

    /**
     * Indicates whether some other object is "equal to" this one.
     *
     * @apiNote
     * It is generally necessary to override the `hashCode` method whenever this
     * method is overridden, so as to maintain the general contract for the `hashCode`
     * method,  which states that equal objects must have equal hash codes.
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }
}

上面介紹了 hashCode 方法註釋上列出的三個通用約定,equals 方法的註釋上也有這麼一句話:「每當重寫 equals 方法時,都需要重寫 hashCode 方法,這樣才沒有破壞 hashCode 方法的通用約定,即:兩個物件為 Equal 的話(呼叫 equals 方法為 true), 那麼這兩個物件分別呼叫 hashCode 方法也需要返回相同的雜湊值」。

所以只重寫 equals 方法不重寫 hashCode 方法的話,可能會造成兩個物件呼叫 equals 方法為 true,而 hashCode 值不同的情形,這樣即可能造成異常的行為。

這個情形是什麼?
兩個內容相等的Person物件p1和p2的hashCode()不同,是因為在Person類中沒有重寫hashCode()方法,它們使用的是Object類繼承下來的hashCode()方法的預設實現。

在Object類中,hashCode()方法的預設實現是將物件的記憶體地址值作為雜湊碼返回。

總結:
就是一個約定而已。也是為了邏輯的自洽。

Reference

Java hashCode方法深入解析
https://www.javabetter.cn/basic-extra-meal/hashcode.html

Java:為什麼重寫 equals 方法時必須同時重寫 hashCode 方法?
https://leileiluoluo.com/posts/always-override-hashcode-when-override-equals.html

相關文章