1. 程式人生 > >重寫hashCode和equals方法

重寫hashCode和equals方法

如果你的物件想雜湊儲存的集合中或者想作為雜湊Map的Key時(HashSet、HashMap、Hashtable等)那麼你必須重寫equals()方法,這樣才能保證唯一性。在重寫equals()方法的同時,必須重寫hashCode()方法?當然,在這種情況下,你不想重寫hashCode()方法,也沒有錯,但是sun建議這麼做,重寫hashCode只是技術要求(為了提高效率)。

      當在雜湊集合中放入key時,將自動檢視key物件的hashCode值,若此時放置的hashCode值和原來已有的hashCode值相等,則自動呼叫equals()方法,若此時返回的為true則表示該key為相同的key值,只會存在一份。

      Object中關於hashCode和equals方法的定義為:

Java程式碼  收藏程式碼
    public boolean equals(Object obj) {  
        return (this == obj);  
    }  
    public native int hashCode();  


基類的hashCode是一個native方法,訪問作業系統底層,它得到的值是與這個物件在記憶體中的地址有關。

      Object的不同子類對於equals和hashCode方法有其自身的實現方式,如Integer和String等。
            equals相等的,hashCode必須相等 
            hashCode不等的,則 equals也必定不等。
            hashCode相等的 equals不一定相等(但最好少出現 hashCode相等的情況)。

      HashMap的put(K, Value)方法提供了一個根據K的hashCode來計算Hash碼的方法hash() 

Java程式碼  收藏程式碼
    transient Entry[] table;  
    public V put(K key, V value) {  
        if (key == null)  
            return putForNullKey(value);        //HashMap支援null的key  
        int hash = hash(key.hashCode());        //根據key的hashCode計算Hash值  
        int i = indexFor(hash, table.length);   //搜尋指定Hash值在對應table中的索引  
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {    //在i索引處Entry不為空,迴圈遍歷e的下一個元素  
            Object k;  
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
                V oldValue = e.value;  
                e.value = value;  
                e.recordAccess(this);  
                return oldValue;  
            }  
        }  
        //若i索引處Entry為null,表明此處還沒有Entry  
        modCount++;  
        addEntry(hash, key, value, i);  //將key、value新增到i索引處  
        return null;  
    }  
      
    static int hash(int h) {  
        h ^= (h >>> 20) ^ (h >>> 12);  
        return h ^ (h >>> 7) ^ (h >>> 4);  
    }  


     對於任意給定的物件,只有它的hashCode()返回值相同,那麼程式呼叫hash(int h)方法所計算得到的Hash碼值總是相同的。接下來會呼叫indexFor(int h, int length)方法來計算該物件應該儲存在table陣列的哪個索引處。 

Java程式碼  收藏程式碼
    static int indexFor(int h, int length) {  
        return h & (length-1);  
    }  


     它總是通過h & (table.length - 1)來得到該物件的儲存位置--而HashMap底層陣列的長度總是2的n次方,這樣就保證了得到的索引值總是位於table陣列的索引之內。
     當通過key-value放入HashMap時,程式就根據key的hashCode()來覺得Entry的儲存位置:若兩個Entry的key的hashCode()相同那麼他們的儲存位置相同;若兩個Entry的key的equals()方法返回true則新新增Entry的value將覆蓋原有Entry的value,但key不會覆蓋;若兩個Entry的key的equals()方法返回false則新加的Entry與集合中原有的Entry形成Entry鏈。

Java程式碼  收藏程式碼
    void addEntry(int hash, K key, V value, int bucketIndex) {  
        Entry<K,V> e = table[bucketIndex];  
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
        if (size++ >= threshold)     //size儲存了HashMap中所包含的key-value對的數量  
            resize(2 * table.length);   //擴充table物件的長度2倍  
    }  


     HashSet的add(E)的實現是通過HashMap的put方法來實現的。(HashSet內部是通過HashMap來實現的,TreeSet則是通過TreeMap來實現的)

Java程式碼  收藏程式碼
    public V get(Object key) {  
        if (key == null)  
            return getForNullKey();  
        int hash = hash(key.hashCode());  
        int i = indexFor(hash, table.length);  
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
            Object k;  
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
                return e.value;  
        }  
        return null;  
    }  


    根據key的hashCode計算器Hash值,然後取得該Hash值在表table的索引,取得該索引i對應的table中的Entry,判斷key的equals()。