爲何要重寫hashcode和equals方法?初級程序員在面試中不多能說清楚。

  我在面試 Java初級開發的時候,常常會問:你有沒有重寫過hashcode方法?很多候選人直接說沒寫過。我就想,或許真的沒寫過,因而就再經過一個問題確認:你在用HashMap的時候,鍵(Key)部分,有沒有放過自定義對象?而這個時候,候選人說放過,因而兩個問題的回答就自相矛盾了。
  
  最近問下來,這個問題廣泛回答不大好,因而在本文裏,就乾脆從hash表講起,講述HashMap的存數據規則,由此你們就天然清楚上述問題的答案了。
  
  1 經過Hash算法來了解HashMap對象的高效性
  
  咱們先複習數據結構裏的一個知識點:在一個長度爲n(假設是10000)的線性表(假設是ArrayList)裏,存放着無序的數字;若是咱們要找一個指定的數字,就不得不經過從頭至尾依次遍從來查找,這樣的平均查找次數是n除以2(這裏是5000)。
  
  咱們再來觀察Hash表(這裏的Hash表純粹是數據結構上的概念,和Java無關)。它的平均查找次數接近於1,代價至關小,關鍵是在Hash表裏,存放在其中的數據和它的存儲位置是用Hash函數關聯的。
  
  咱們假設一個Hash函數是x*x%5。固然實際狀況裏不可能用這麼簡單的Hash函數,咱們這裏純粹爲了說明方便,而Hash表是一個長度是11的線性表。若是咱們要把6放入其中,那麼咱們首先會對6用Hash函數計算一下,結果是1,因此咱們就把6放入到索引號是1這個位置。一樣若是咱們要放數字7,通過Hash函數計算,7的結果是4,那麼它將被放入索引是4的這個位置。這個效果以下圖所示。
  
  這樣作的好處很是明顯。好比咱們要從中找6這個元素,咱們能夠先經過Hash函數計算6的索引位置,而後直接從1號索引裏找到它了。
  
  不過咱們會遇到「Hash值衝突」這個問題。好比通過Hash函數計算後,7和8會有相同的Hash值,對此Java的HashMap對象採用的是」鏈地址法「的解決方案。效果以下圖所示。
  
  具體的作法是,爲全部Hash值是i的對象創建一個同義詞鏈表。假設咱們在放入8的時候,發現4號位置已經被佔,那麼就會新建一個鏈表結點放入8。一樣,若是咱們要找8,那麼發現4號索引裏不是8,那會沿着鏈表依次查找。
  
  雖然咱們仍是沒法完全避免Hash值衝突的問題,可是Hash函數設計合理,仍能保證同義詞鏈表的長度被控制在一個合理的範圍裏。這裏講的理論知識並不是無的放矢,你們能在後文裏清晰地瞭解到重寫hashCode方法的重要性。
  
  2 爲何要重寫equals和hashCode方法
  
  當咱們用HashMap存入自定義的類時,若是不重寫這個自定義類的equals和hashCode方法,獲得的結果會和咱們預期的不同。咱們來看WithoutHashCode.java這個例子。
  
  在其中的第2到第18行,咱們定義了一個Key類;在其中的第3行定義了惟一的一個屬性id。當前咱們先註釋掉第9行的equals方法和第16行的hashCode方法。java

  1   import java.util.HashMap;
  
  2   class Key {
  
  3       private Integer id;
  
  4       public Integer getId()
  
  5   {return id; }
  
  6       public Key(Integer id)
  
  7   {this.id = id;  }
  
  8   //故意先註釋掉equals和hashCode方法
  
  9   //  public boolean equals(Object o) {
  
  10  //      if (o == null || !(o instanceof Key))
  
  11  //      { return false; }
  
  12  //      else
  
  13  //      { return this.getId().equals(((Key) o).getId());}
  
  14  //  }
  
  15
  
  16  //  public int hashCode()
  
  17  //  { return id.hashCode(); }
  
  18  }
  
  19
  
  20  public class WithoutHashCode {
  
  21      public static void main(String[] args) {
  
  22          Key k1 = new Key(1);
  
  23          Key k2 = new Key(1);
  
  24          HashMap<Key,String> hm = new HashMap<Key,String>();
  
  25          hm.put(k1, "Key with id is 1");
  
  26          System.out.println(hm.get(k2));
  
        public class Factory
        private CustomerOperation _www.mcyulegw.com customerOperation = null;
        private OrderOperation www.dfgjpt.com_orderOperation = null;
        public Customer Customers
            Factory factory = new Factory();
            factory .Orders.Get(www.yongshiyule178.com);
            factory .Customers.Get();
            Console.ReadLine(www.yongshi123.cn );程序員

  在main函數裏的第22和23行,咱們定義了兩個Key對象,它們的id都是1,就比如它們是兩把相同的都能打開同一扇門的鑰匙。
  
  在第24行裏,咱們經過泛型建立了一個HashMap對象。它的鍵部分能夠存放Key類型的對象,值部分能夠存儲String類型的對象。
  
  在第25行裏,咱們經過put方法把k1和一串字符放入到hm裏; 而在第26行,咱們想用k2去從HashMap裏獲得值;這就比如咱們想用k1這把鑰匙來鎖門,用k2來開門。這是符合邏輯的,但從當前結果看,26行的返回結果不是咱們想象中的那個字符串,而是null。
  
  緣由有兩個—沒有重寫。第一是沒有重寫hashCode方法,第二是沒有重寫equals方法。
  
  當咱們往HashMap裏放k1時,首先會調用Key這個類的hashCode方法計算它的hash值,隨後把k1放入hash值所指引的內存位置。
  
  關鍵是咱們沒有在Key裏定義hashCode方法。這裏調用的還是Object類的hashCode方法(全部的類都是Object的子類),而Object類的hashCode方法返回的hash值實際上是k1對象的內存地址(假設是1000)。
  
  若是咱們隨後是調用hm.get(k1),那麼咱們會再次調用hashCode方法(仍是返回k1的地址1000),隨後根據獲得的hash值,能很快地找到k1。
  
  但咱們這裏的代碼是hm.get(k2),當咱們調用Object類的hashCode方法(由於Key裏沒定義)計算k2的hash值時,其實獲得的是k2的內存地址(假設是2000)。因爲k1和k2是兩個不一樣的對象,因此它們的內存地址必定不會相同,也就是說它們的hash值必定不一樣,這就是咱們沒法用k2的hash值去拿k1的緣由。
  
  當咱們把第16和17行的hashCode方法的註釋去掉後,會發現它是返回id屬性的hashCode值,這裏k1和k2的id都是1,因此它們的hash值是相等的。
  
  咱們再來更正一下存k1和取k2的動做。存k1時,是根據它id的hash值,假設這裏是100,把k1對象放入到對應的位置。而取k2時,是先計算它的hash值(因爲k2的id也是1,這個值也是100),隨後到這個位置去找。
  
  但結果會出乎咱們意料:明明100號位置已經有k1,但第26行的輸出結果依然是null。其緣由就是沒有重寫Key對象的equals方法。
  
  HashMap是用鏈地址法來處理衝突,也就是說,在100號位置上,有可能存在着多個用鏈表形式存儲的對象。它們經過hashCode方法返回的hash值都是100。
  
  當咱們經過k2的hashCode到100號位置查找時,確實會獲得k1。但k1有可能僅僅是和k2具備相同的hash值,但未必和k2相等(k1和k2兩把鑰匙未必能開同一扇門),這個時候,就須要調用Key對象的equals方法來判斷二者是否相等了。
  
  因爲咱們在Key對象裏沒有定義equals方法,系統就不得不調用Object類的equals方法。因爲Object的固有方法是根據兩個對象的內存地址來判斷,因此k1和k2必定不會相等,這就是爲何依然在26行經過hm.get(k2)依然獲得null的緣由。
  
  爲了解決這個問題,咱們須要打開第9到14行equals方法的註釋。在這個方法裏,只要兩個對象都是Key類型,並且它們的id相等,它們就相等。
  
  3 對面試問題的說明
  
  因爲在項目裏常常會用到HashMap,因此我在面試的時候必定會問這個問題∶你有沒有重寫過hashCode方法?你在使用HashMap時有沒有重寫hashCode和equals方法?你是怎麼寫的?
  
  根據問下來的結果,我發現初級程序員對這個知識點廣泛沒掌握好。重申一下,若是你們要在HashMap的「鍵」部分存放自定義的對象,必定要在這個對象裏用本身的equals和hashCode方法來覆蓋Object裏的同名方法。面試

相關文章
相關標籤/搜索