重寫equals方法和重寫hashcode方法沒有必然因果關係

看到很多博客把標題設置成 "爲什麼重寫equals方法後一定要重寫hashcode方法",雖然這是個面試題,事實上,若不看情況地將該命題一概而論,很容易造成不必要的誤解。上述標題易把equals和hashCode之間形成因果關係,equals是因,hashCode是果。然而看過源碼的都應該知道,Object的equals方法默認是返回 this == otherObj,而hashCode是一個native本地方法,由底層的c++實現,是完全獨立的兩個方法,沒有必然的因果關係。

要討論兩者的關係,離不開無序不重複集合,比如HashSet,HashMap等等,一個較好的例子是在無序不重複集合中存取自定義對象:(設有自定義對象Person,有字段name,age等,假設業務要求name和age都相等纔算兩個Person相等)

首先大致預備下基礎知識

  • HashMap和HashSet等是如何存數據的?簡單講分兩步,第一步,通過對象person的哈希值得到邏輯地址A。第二步,若地址A上沒其他對象,則存入成功,否則使用鏈表(jdk1.7)或者紅黑樹(jdk1.8)處理散列衝突,如何處理?以A爲起點一個個遍歷查找掛在A上的鏈表或者二分查找掛在A上的紅黑樹,若鏈表或紅黑樹上每個節點都不等於person(調用equals比較),則把person掛上去,也就是put成功,否則找到相等的節點,用person更新它的值

比如HashSet想存一個Person,它首先得知道這個Person放哪裏,於是會調用Person的hashCode方法返回一個hash值,並和length - 1相與生成它的邏輯地址,如下圖。如果幸運,地址沒衝突,HashSet就確定把Person放這裏了

  • 像Integer、String、Byte、Boolean等對象,java已經重寫了對應的hashCode方法,使得值相同的不同堆對象的哈希值仍相同,自定義對象調用的是Object的hashCode方法,也就是c++實現的本地方法,只要是不同的堆對象,哈希值就不會相同

瞭解完基礎知識後不難發現,

之所以要重寫hashCode是爲了保證多個相等的Person堆對象的哈希值也相等

若不重寫hashCode,Person調用的就是c++實現的本地方法,即使你兩個new出來的Person對象name和age都相同,但由於是兩個不同的堆對象,c++的那套代碼會hash出不同的結果,所以邏輯地址也就不同,HashMap看到是不同的邏輯地址就會把兩者存到不同地方,於是一個HashMap裏出現兩個一樣的Person,第一,與紅字業務衝突,第二,與無序不重複集合這個定義衝突,第三,你在get操作的時候,它不知道get哪一個給你

之所以要重寫equals,一是爲了業務邏輯,即自定義對象要怎樣比較纔算相等,畢竟不止一個字段。二是同樣爲了保證在HashMap或HashSet中不出現重複的自定義對象

若不重寫equals,則HashMap或HashSet在遍歷鏈表或紅黑樹處理衝突時就不知道怎麼比較Person對象纔算相同,只能調用Object的equals方法,使用 == 比較引用地址,導致name和age都相等的Person仍會被判定爲不相同,同樣與紅字業務衝突。再者,假設你已經重寫了正確的hashCode方法,即確保了name和age相同的Person堆對象都會被hash到同一邏輯地址A,此時假設來了三個name和age一模一樣的Person,第二個Person被映射到A後發現已經有對象了,於是嘗試解決哈希衝突,調用equals比較,通過 == 比較後發現不相等,HashMap鬆了口氣,直接開心地把第二個Person掛到了A的鏈表或紅黑樹上,這樣,HashMap又出現了兩個一樣的Person!!於是你發現,重寫hashCode後一定還要重寫equals方法

由此可見,重寫hashCode和重寫equals方法之間沒有必然的因果關係,就綠色字體而言,那標題是不是又可以改成 "爲什麼重寫hashCode後還一定要重寫equals?" 呢?對於HashMap的內部邏輯,hashCode和equals本身就是一種對等的關係,缺一不可。一個更好的問法應當是:"爲什麼在編程的時候有時會需要重寫hashCode和equals方法?"

發佈了38 篇原創文章 · 獲贊 42 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章