因為最近在整理Java集合的源碼, 所以今天再來談談這個古老的話題,因為后面講HashMap會用到這個知識點, 所以重新梳理下。
如果不被重寫(原生Object)的hashCode和equals是什么樣的?
如果不被重寫(原生Object)的hashCode和equals是什么樣的?
- 不被重寫(原生)的hashCode值是根據內存地址換算出來的一個值。
- 不被重寫(原生)的equals方法是嚴格判斷一個對象是否相等的方法(object1 == object2)。
為什么需要重寫equals和hashCode方法?
在我們的業務系統中判斷對象時有時候需要的不是一種嚴格意義上的相等,而是一種業務上的對象相等。在這種情況下,原生的equals方法就不能滿足我們的需求了
所以這個時候我們需要重寫equals方法,來滿足我們的業務系統上的需求。那么為什么在重寫equals方法的時候需要重寫hashCode方法呢?
在我們的業務系統中判斷對象時有時候需要的不是一種嚴格意義上的相等,而是一種業務上的對象相等。在這種情況下,原生的equals方法就不能滿足我們的需求了
所以這個時候我們需要重寫equals方法,來滿足我們的業務系統上的需求。那么為什么在重寫equals方法的時候需要重寫hashCode方法呢?
我們先來看一下Object.hashCode的通用約定(摘自《Effective Java》第45頁)
- 在一個應用程序執行期間,如果一個對象的equals方法做比較所用到的信息沒有被修改的話,那么,對該對象調用hashCode方法多次,它必須始終如一地返回 同一個整數。在同一個應用程序的多次執行過程中,這個整數可以不同,即這個應用程序這次執行返回的整數與下一次執行返回的整數可以不一致。
- 如果兩個對象根據equals(Object)方法是相等的,那么調用這兩個對象中任一個對象的hashCode方法必須產生同樣的整數結果。
- 如果兩個對象根據equals(Object)方法是不相等的,那么調用這兩個對象中任一個對象的hashCode方法,不要求必須產生不同的整數結果。然而,程序員應該意識到這樣的事實,對於不相等的對象產生截然不同的整數結果,有可能提高散列表(hash table)的性能。
如果只重寫了equals方法而沒有重寫hashCode方法的話,則會違反約定的第二條:相等的對象必須具有相等的散列碼(hashCode)。
同時對於HashSet和HashMap這些基於散列值(hash)實現的類。HashMap的底層處理機制是以數組的方法保存放入的數據的(Node<K,V>[] table),其中的關鍵是數組下標的處理。數組的下標是根據傳入的元素hashCode方法的返回值再和特定的值異或決定的。如果該數組位置上已經有放入的值了,且傳入的鍵值相等則不處理,若不相等則覆蓋原來的值,如果數組位置沒有條目,則插入,並加入到相應的鏈表中。檢查鍵是否存在也是根據hashCode值來確定的。所以如果不重寫hashCode的話,
可能導致HashSet、HashMap不能正常的運作、
如果我們將某個自定義對象存到HashMap或者HashSet及其類似實現類中的時候,
如果該對象的屬性參與了hashCode的計算,那么就不能修改該對象參數hashCode計算的屬性了。有可能會移除不了元素,導致內存泄漏。
接着來看一個代碼片段:


運行這段代碼發現結果返回的是null。
再來看一下HashMap中的get源碼:

get的時候會先比較hashCode然后再去比較equals, 返回結果為null其實都是hashCode惹的禍。

以Java.lang.Object來理解, JVM每次new一個Object, 都會將Object丟到一個哈希表中去, 這樣的話,下次做Object的比較或者取這個對象的時候, 它會根據對象的hashcode再從Hash表中取這個對象。這樣做的目的是提高取對象的效率。
1.new Object(),JVM根據這個對象的Hashcode值,放入到對應的Hash表對應的Key上,如果不同的對象確產生了相同的hash值,也就是發生了Hash key相同導致沖突的情況,那么就在這個Hash key的地方產生一個鏈表,將所有產生相同hashcode的對象放到這個單鏈表上去,串在一起。
2.比較兩個對象的時候,首先根據他們的hashcode去hash表中找他的對象,當兩個對象的hashcode相同,那么就是說他們這兩個對象放在Hash表中的同一個key上,那么他們一定在這個key上的鏈表上。那么此時就只能根據Object的equal方法來比較這個對象是否equal。當兩個對象的hashcode不同的話,肯定他們不能equals.