Java重寫equals方法時為什麼要重寫hashcode方法

瓜瓜東西發表於2014-03-27
equals 方法和 hashcode 方法沒有必然關係,並不是說重寫 equals 方法就一定要重寫 hashcode 方法。

用途不同, equals 用來比較兩個物件是否相等,在大多數 JDK 的集合類中判斷唯一性的時候使用的都是 equals 方法。而 hashcode 方法用來計算物件的 Hash 值,基於 Hash 演算法存放資料的集合會用到,比如 HashMap、HashSet。

equals 方法很好理解,區別於直接比較物件記憶體地址的 == ,它被設計為用來比較物件內容語義上的相等。

而要理解 hashcode 方法,首先你要知道什麼是雜湊演算法,瞭解一下 HashMap 底下的儲存結構和存放讀取資料的過程(對 key 呼叫 hashcode 得雜湊值,找到該雜湊值對應的桶,往桶裡放 value)。你可以認為 hashcode 是為了給物件分類用的。

重寫hashCode()時最重要的原因就是:無論何時,對同一個物件呼叫hashCode()都應該生成同樣的值。如果在將一個物件用put()方法添 加進HashMap時產生一個hashCode()值,而用get()取出時卻產生了另外一個 hashCode()值,那麼就無法重新取得該物件了。所以,如果你的hashCode()方法依賴於物件中易變的資料,那使用者就要小心了,因為此資料發 生變化時,hashCode()就會產生一個不同的hash碼,相當於產生了一個不同的“鍵”。        Object的hashCode()方法,返回的是當前物件的記憶體地址。下次如果我們需要取一個一樣的“鍵”對應的鍵值對的時候,我們就無法得到一樣的 hashCode值了。因為我們後來建立的“鍵”物件已經不是存入HashMap中的那個記憶體地址的物件了。        我們看一個簡單的例子,就能更加清楚的理解上面的意思。假定我們寫了一個類:Person (人),我們判斷一個物件“人”是否指向同一個人,只要知道這個人的身份證號一直就可以了。        先來個沒有重寫Code類的hashcode()的例子吧,看看是什麼效果:

Java程式碼  收藏程式碼
  1. package com.fit;  
  2.   
  3. import java.util.HashMap;  
  4.   
  5. /** 
  6.  * 身份證類 
  7.  *  
  8.  * @author ZYD 
  9.  *  
  10.  */  
  11. public class Code {  
  12.   
  13.     /** 
  14.      * 身份證號碼,一旦確定就不能更改 
  15.      */  
  16.     private final int id;  
  17.   
  18.     public int getId() {  
  19.         return id;  
  20.     }  
  21.   
  22.     /** 
  23.      * 通過構造方法確定身份證號碼 
  24.      *  
  25.      * @param id 
  26.      */  
  27.     public Code(int id) {  
  28.         this.id = id;  
  29.     }  
  30.   
  31.     /** 
  32.      * 重寫equals()方法 
  33.      */  
  34.     public boolean equals(Object o) {  
  35.         // 如果地址一樣,則兩個物件相同  
  36.         if (this == o) {  
  37.             return true;  
  38.         }  
  39.         // 如果兩個物件是同一型別,則比較其屬性值是否都相同。如果都相同,則說明兩個物件也相同;否則,說明這兩個物件不相同。  
  40.         if (o instanceof Code) {  
  41.             Code co = (Code) o;  
  42.             boolean b = (co.id == this.id);  
  43.             return b;  
  44.         }  
  45.         return false;  
  46.     }  
  47.   
  48.     /** 
  49.      * 重寫toString()方法 
  50.      */  
  51.     public String toString() {  
  52.         return "【身份證】:" + id;  
  53.     }  
  54.       
  55.     /** 
  56.      * 測試 
  57.      * @param args 
  58.      */  
  59.     public static void main(String[] args) {  
  60.           
  61.          HashMap<Code, Person> map = new HashMap<Code, Person>();  
  62.            
  63.          Person p1 = new Person(new Code(10001),"張三");  
  64.          Person p2 = new Person(new Code(10002),"李四");  
  65.            
  66.          map.put(p1.getCode(), p1);  
  67.          map.put(p2.getCode(), p2);  
  68.            
  69.          System.out.println("HashMap 中存放的人員資訊:\n"+map);  
  70.            
  71.          //張三改名為張山,身份證號不變。  
  72.          Person p3 = new Person(new Code(10001),"張山");  
  73.          map.put(p3.getCode(), p3);  
  74.            
  75.          System.out.println("張三改名為張山後 HashMap 中存放的人員資訊:\n"+map);  
  76.            
  77.          //查詢身份證為10001 的人員資訊  
  78.          System.out.println("查詢身份證為:10001 的人員資訊:"+map.get(new Code(10001)));  
  79.     }  
  80. }  
  81.   
  82. /** 
  83.  * 人類 
  84.  * @author Administrator 
  85.  * 
  86.  */  
  87. class Person {  
  88.   
  89.     /** 
  90.      * 每一個成人都有一個身份證 
  91.      */  
  92.     private Code code;  
  93.   
  94.     /** 
  95.      * 姓名 
  96.      */  
  97.     private String name;  
  98.   
  99.     public Code getCode() {  
  100.         return code;  
  101.     }  
  102.   
  103.     public void setCode(Code code) {  
  104.         this.code = code;  
  105.     }  
  106.   
  107.     public String getName() {  
  108.         return name;  
  109.     }  
  110.   
  111.     public void setName(String name) {  
  112.         this.name = name;  
  113.     }  
  114.   
  115.     public Person() {  
  116.   
  117.     }  
  118.   
  119.     public Person(Code code, String name) {  
  120.         this.code = code;  
  121.         this.name = name;  
  122.     }  
  123.   
  124.     /** 
  125.      * 重寫equals()方法 當兩個人得身份證號相同以及姓名相同時,表示這兩個人是同一個人。 
  126.      */  
  127.     public boolean equals(Object o) {  
  128.         if (o == this) {  
  129.             return true;  
  130.         }  
  131.         if (o instanceof Person) {  
  132.             Person p = (Person) o;  
  133.             boolean b = this.code.equals(p.code) && this.name.equals(p.name);  
  134.             return b;  
  135.         }  
  136.         return false;  
  137.     }  
  138.   
  139.     /** 
  140.      * 重寫toString()方法 
  141.      */  
  142.     public String toString() {  
  143.         return "【姓名】:" + name + "  ";  
  144.     }  
  145. }  

 

 

執行結果:

 

HashMap 中存放的人員資訊: {【身份證】:10002=【姓名】:李四  , 【身份證】:10001=【姓名】:張三  } 張三改名為張山後 HashMap 中存放的人員資訊: {【身份證】:10002=【姓名】:李四  , 【身份證】:10001=【姓名】:張三  , 【身份證】:10001=【姓名】:張山  } 查詢身份證為:10001 的人員資訊:null

 

從上面的結果可以看出:

我們所做的更新和查詢操作都失敗了。失敗的原因就是我們的身份證類: Code 沒有覆寫 hashCode() 方法。這個時候,當查詢一樣的身份證號碼的鍵值對的時候,使用的是預設的物件的記憶體地址來進行定位。這樣,後面的所有的身份證號物件

new Code(10001) 產生的 hashCode () 值都是不一樣的,所以導致操作失敗。

 

 

 重寫Code類的hashcode(),程式碼上:

 

Java程式碼  收藏程式碼
  1. package com.fit;  
  2.   
  3. import java.util.HashMap;  
  4.   
  5. /** 
  6.  * 身份證類 
  7.  *  
  8.  * @author ZYD 
  9.  *  
  10.  */  
  11. public class Code {  
  12.   
  13.     /** 
  14.      * 身份證號碼,一旦確定就不能更改 
  15.      */  
  16.     private final int id;  
  17.   
  18.     public int getId() {  
  19.         return id;  
  20.     }  
  21.   
  22.     /** 
  23.      * 通過構造方法確定身份證號碼 
  24.      *  
  25.      * @param id 
  26.      */  
  27.     public Code(int id) {  
  28.         this.id = id;  
  29.     }  
  30.   
  31.     /** 
  32.      * 重寫equals()方法 
  33.      */  
  34.     public boolean equals(Object o) {  
  35.         // 如果地址一樣,則兩個物件相同  
  36.         if (this == o) {  
  37.             return true;  
  38.         }  
  39.         // 如果兩個物件是同一型別,則比較其屬性值是否都相同。如果都相同,則說明兩個物件也相同;否則,說明這兩個物件不相同。  
  40.         if (o instanceof Code) {  
  41.             Code co = (Code) o;  
  42.             boolean b = (co.id == this.id);  
  43.             return b;  
  44.         }  
  45.         return false;  
  46.     }  
  47.   
  48.     /** 
  49.      * 重寫hashcode()方法,以身份證號碼作為hash碼。 
  50.      *  
  51.      * @return 
  52.      */  
  53.     public int hashCode() {  
  54.         return id;  
  55.     }  
  56.   
  57.     /** 
  58.      * 重寫toString()方法 
  59.      */  
  60.     public String toString() {  
  61.         return "【身份證】:" + id;  
  62.     }  
  63.       
  64.     /** 
  65.      * 測試 
  66.      * @param args 
  67.      */  
  68.     public static void main(String[] args) {  
  69.           
  70.          HashMap<Code, Person> map = new HashMap<Code, Person>();  
  71.            
  72.          Person p1 = new Person(new Code(10001),"張三");  
  73.          Person p2 = new Person(new Code(10002),"李四");  
  74.            
  75.          map.put(p1.getCode(), p1);  
  76.          map.put(p2.getCode(), p2);  
  77.            
  78.          System.out.println("HashMap 中存放的人員資訊:\n"+map);  
  79.            
  80.          //張三改名為張山,身份證號不變。  
  81.          Person p3 = new Person(new Code(10001),"張山");  
  82.          map.put(p3.getCode(), p3);  
  83.            
  84.          System.out.println("張三改名為張山後 HashMap 中存放的人員資訊:\n"+map);  
  85.            
  86.          //查詢身份證為10001 的人員資訊  
  87.          System.out.println("查詢身份證為:10001 的人員資訊:"+map.get(new Code(10001)));  
  88.     }  
  89. }  
  90.   
  91. /** 
  92.  * 人類 
  93.  * @author Administrator 
  94.  * 
  95.  */  
  96. class Person {  
  97.   
  98.     /** 
  99.      * 每一個成人都有一個身份證 
  100.      */  
  101.     private Code code;  
  102.   
  103.     /** 
  104.      * 姓名 
  105.      */  
  106.     private String name;  
  107.   
  108.     public Code getCode() {  
  109.         return code;  
  110.     }  
  111.   
  112.     public void setCode(Code code) {  
  113.         this.code = code;  
  114.     }  
  115.   
  116.     public String getName() {  
  117.         return name;  
  118.     }  
  119.   
  120.     public void setName(String name) {  
  121.         this.name = name;  
  122.     }  
  123.   
  124.     public Person() {  
  125.   
  126.     }  
  127.   
  128.     public Person(Code code, String name) {  
  129.         this.code = code;  
  130.         this.name = name;  
  131.     }  
  132.   
  133.     /** 
  134.      * 重寫equals()方法 當兩個人得身份證號相同以及姓名相同時,表示這兩個人是同一個人。 
  135.      */  
  136.     public boolean equals(Object o) {  
  137.         if (o == this) {  
  138.             return true;  
  139.         }  
  140.         if (o instanceof Person) {  
  141.             Person p = (Person) o;  
  142.             boolean b = this.code.equals(p.code) && this.name.equals(p.name);  
  143.             return b;  
  144.         }  
  145.         return false;  
  146.     }  
  147.   
  148.     /** 
  149.      * 重寫toString()方法 
  150.      */  
  151.     public String toString() {  
  152.         return "【姓名】:" + name + "  ";  
  153.     }  
  154. }  

相關文章