1. 程式人生 > >覆蓋(不是過載)了equals方法,請一定要覆蓋hashCode方法

覆蓋(不是過載)了equals方法,請一定要覆蓋hashCode方法

為了能讓集合框架中的類如HashMap正常工作,必須保證同時覆蓋equals()和hashCode(),而且注意不要由於寫錯了引數型別,而過載了這兩個方法,卻並沒有覆蓋它們,比如: 
public boolean equals(Object obj) 寫成了public boolean equals(ClassXXX obj) 。 

為什麼在覆蓋equals時一定也要覆蓋hashCode呢? 下面用HashMap來闡述原因,首先假設key1和key2的值在業務邏輯領域是相等的,即它們應該是同一個物件,HashMap已經儲存了key1,現在要查詢key2是否存在,正確的結果應該是存在: 
Java中的HashMap實際上是一個連結串列陣列,即首先HashMap是一個數組,然後陣列中的每一個元素是一個連結串列(更通用的概念可以稱為桶bucket,Java中的HashMap用Entry類描述連結串列的結點結構),HashMap在執行Put,Contains之類的操作時,會首先根據你提供的Key計算hashCode值,然後根據這個hashCode值在陣列中找到某一個連結串列或桶(通常是找到連結串列的起始結點),這一步操作利用了hashCode()方法,如果你覆蓋了就會用你提供的方法,在找到某一個連結串列的起始結點後,就會遍歷連結串列,然後通過equals方法來尋找是否存在與Key的值相等的結點,如果執行equals方法後的結果相等,HashMap就認為已經存在這個元素,這一步如果你覆蓋了equals方法就會用到你提供的equals方法。 
通過上面的描述,我們發現equals方法和hashCode方法如果不同時按你自己邏輯覆蓋的話,HashMap就會出問題。比如你只覆蓋了equals方法而沒有覆蓋hashCode方法,那麼HashMap在第一步尋找連結串列的時候會出錯,有同樣值的兩個物件Key1和Key2並不會指向同一個連結串列或桶,因為你沒有提供自己的hashCode方法,那麼就會使用Object的hashCode方法,該方法是根據記憶體地址來比較兩個物件是否一致,由於Key1和Key2有不桶的記憶體地址,所以會指向不同的連結串列,這樣HashMap會認為key2不存在,雖然我們期望Key1和Key2是同一個物件;反之如果只覆蓋了hashCode方法而沒有覆蓋equals方法,那麼雖然第一步操作會使Key1和Key2找到同一個連結串列,但是由於equals沒有覆蓋,那麼在遍歷連結串列的元素時,key1.equals(key2)也會失敗(事實上Object的equals方法也是比較記憶體地址),從而HashMap認為不存在Key2物件,這同樣也是不正確的。 

以下內容摘自<<Effective Java>>
 
覆蓋equals時總要覆蓋hashCode,一個很常見的錯誤根源在沒有覆蓋hashCode方法。在每個覆蓋了equals方法的類中,也必須覆蓋hashCode方法。如果不這樣做的話,就會違反Object.hashCode的通用約定,從而導致該類無法結合所有基於雜湊的集合一起正常工作,這樣的集合包括HashMap、HashSet和Hashtable。 

下面是約定的內容,摘自Object規範[JavaSE6]: 
1)在應用程式的執行期間,只要物件的equals方法所用到的資訊沒有被修改,那麼對這同一個物件呼叫多次,hashCode方法都必須始終如一地返回同一個整數。在同一個應用程式的多次執行過程中,每次執行所返回的整數可以不一致。 

2)如果兩個物件根據equals(Object)方法比較是相等
的,那麼呼叫這兩個物件中任意一個物件的hashCode方法都必須產生同樣的整數結果。 

3)如果兩個物件根據equals(Object)方法比較是不相等的,那麼呼叫這兩個物件中的任意一個物件的hashCode方法,則不一定要產生不同的整數結果。但是程式設計師應該知道,給不相同的物件產生截然不同的整數結果,有可能提供散列表(hash table)的效能。 

如果覆蓋equals沒有覆蓋hashCode,將會違反上面的第2條:相等的物件必須具有相等的雜湊碼(hashCode)。Object類裡面的預設eqals方法是比較記憶體地址是否相等,預設的hashCode方法則是根據記憶體地址產生一個整數,所以Object類本身當然是符合上面規則的。
當你覆蓋了equals後,記憶體地址不同的物件可能會相等,而如果這時你沒有覆蓋hashCode方法的話,hashCode還是根據記憶體地址來生成,就會出現相等的物件具有不同的雜湊碼的情況。 

下面給出了一個過載equals和hashCode的樣本: 
Java程式碼  收藏程式碼
  1. package core;  
  2. public class OverrideEqualsAndHashCodeSample {  
  3.     private String name;  
  4.     private String region;  
  5.     private String position;  
  6.     public String getName() {  
  7.         return name;  
  8.     }  
  9.     public void setName(String name) {  
  10.         this.name = name;  
  11.     }  
  12.     public String getRegion() {  
  13.         return region;  
  14.     }  
  15.     public void setRegion(String region) {  
  16.         this.region = region;  
  17.     }  
  18.     public String getPosition() {  
  19.         return position;  
  20.     }  
  21.     public void setPosition(String position) {  
  22.         this.position = position;  
  23.     }  
  24.     @Override  
  25.     public boolean equals(Object o) {  
  26.         if(o==nullreturn false;  
  27.         if(!(o instanceof OverrideEqualsAndHashCodeSample)) return false;  
  28.         if(this==o) return true;  
  29.         OverrideEqualsAndHashCodeSample o2=(OverrideEqualsAndHashCodeSample)o;  
  30.         if(name.equalsIgnoreCase(o2.name)  
  31.             && region.equalsIgnoreCase(o2.region)  
  32.             && position.equalsIgnoreCase(o2.position))  
  33.             return true;  
  34.         return false;  
  35.     }  
  36.     @Override  
  37.     public int hashCode() {  
  38.       int result=17;  
  39.       result=31*result+name!=null?name.hashCode():0;  
  40.       result=31*result+region!=null?region.hashCode():0;  
  41.       result=31*result+position!=null?position.hashCode():0;  
  42.       return result;  
  43.     }  
  44. }