java 中為什麼重寫 equals 後需要重寫 hashCode

香吧香發表於2022-04-21

本文為博主原創,未經允許不得轉載:

1. equals 和 hashCode 方法之間的關係

  這兩個方法都是 Object 的方法,意味著 若一個物件在沒有重寫 這兩個方法時,都會預設採用 Object 類中的方法實現,它們的關係為:

  1. 如果兩個物件通過equals()方法比較相等,那麼這兩個物件的hashCode一定相同。

  2. 如果兩個物件hashCode相同,不能證明兩個物件是同一個物件(不一定相等),只能證明兩個物件在雜湊結構中儲存在同一個地址(不同物件hashCode相同的情況稱為hash衝突)。

2.為什麼重寫equals 後需要重寫 hashCode

  Effective Java 第三版 中 描述為什麼重寫equals 方法後必須重寫hashCode 方法:

每個覆蓋了equals方法的類中,必須覆蓋hashCode。如果不這麼做,就違背了hashCode的通用約定,也就是上面註釋中所說的。
進而導致該類無法結合所以與雜湊的集合一起正常運作,這裡指的是HashMap、HashSet、HashTable、ConcurrentHashMap。

  上面註釋 為 Object 類中 hashCode 方法註釋:

      If two objects are equal according to the {@code equals(Object)}
     *     method, then calling the {@code hashCode} method on each of
     *     the two objects must produce the same integer result.

  結論:如果重寫equals不重寫hashCode它與雜湊集合無法正常工作。

3. 以 HashMap 為例進行論證分析

  檢視 hashMap 的 put 方法

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

  檢視hash 方法的實現:

  static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

  檢視 get 方法的實現:

   public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

  java中HashMap的資料結構是陣列+連結串列+紅黑樹;這種資料結構,每個鍵值對都會被存在相應的地址中,從程式碼中可以看出HashMap是通過key的hashCode以及自身的容量來決定當前鍵值的儲存索引(桶)的,確定桶的位置後,再進入桶中同時判斷hashCode和equals兩個方法。那也就是說,如果hashCode不同,那麼HashMap就一定會建立一個新的Node鍵值物件。

  HashMap在put一個鍵值對時,會先根據鍵的hashCode和equals方法來同時判斷該鍵在容器中是否已經存在,如果存在則覆蓋,反之新建。所以如果我們在重寫equals方法時,沒有重寫hashCode方法,那麼hashCode方法還是會預設使用Object提供的原始方法,而Object提供的hashCode方法返回值是不會重複的(也就是說每個物件返回的值都不一樣)。所以就會導致每個物件在HashMap中都會是一個新的鍵。

  反向論證:若一個類中重寫了 equals 方法,沒有重寫hashCode方法;且該類的兩個物件具有不同屬性但 hashCode 相等,在hashMap 以該物件為鍵進行儲存時,會出現hash衝突現象,但發現該類重寫了equals 方法,且通過該類的equals 比較之後也是相等,就會出現 hashMap 中只儲存了一個物件,採用get 方法獲取時,就會獲取到別的物件,從而導致獲取物件錯亂。

  

  因此 重寫equals 方法必須重寫 hashCode 方法,用來保證兩個物件通過equals()方法比較相等,那麼這兩個物件的hashCode一定相同 這一原則;

 

相關文章