Java Hash集合的equals()與hashCode() 方法


 

  Java 集合實現類,無論是HashSet、HashMap等所有的Hash算法實現的集合類(后面簡稱Hash集合),加入的對象必須實現 hashCode() 與 equals() 方法,稍微不同的地方是:HashSet 需要對整個對象實現兩個方法,而HashMap 只需要對作為key的對象實現這兩個方法。因為向Hash集合存入一個元素時,Hash集合會調用該對象的hashCode()方法來得到該對象的hashCode的值,然后根據hashCode的值決定該對象在Hash集合中存儲位置。如果兩個元素通過equals()方法比較返回true,但它們的hashCode()方法返回值不相等,Hash集合將會把它們視為不同的對象,兩個元素都可以添加到Hash集合里。所以Hash集合判斷兩個元素是否相等的標准是:兩個對象通過equals()方法比較相等,並且兩個元素的hashCode()方法返回值也相等。

 

重寫hashCode()方法的規則

① 同一個對象多次調用hashCode()方法返回值相同

② 兩個對象通過equals()方法比較返回true 時,這兩個對象的hashCode()方法返回相等的值

③ 對象中用於equals() 方法比較標准的變量,都應該用於計算hashCode值

 

重寫hashCode方法一般步驟

① 把對象內每個有意義的變量計算出一個int 型的hashCode值

② 用第1步計算出來的多個hashCode值組合成一個hashCode值返回

③ 為了避免直接相加產生偶然相等,可以通過為各變量的hashCode值乘以任意質數再相加

 

案例:

① 首先來看一個沒有重寫equals() 與 hashCode() 方法的對象,添加到HashSet 時會出現什么情況。

我們創建一個Person類,包含兩個成員變量,id 與 name,如果兩個對象的這兩個屬性都不等我們才認為它們是不同的對象,我們沒有重寫equals() 與 hashCode() 方法,但是我們希望HashSet 集合對同一個對象只能保存一份,有相同對象加入時,加入失敗。

 1 class Person
 2 {
 3     private String name;
 4     private int id; 
 5     
 6     public Person()
 7     {}
 8     
 9     public Person(int id,String name)
10     {
11         this.id = id;
12         this.name = name;
13     }
14     
15     public int getId() {
16         return id;
17     }
18 
19     public void setId(int id) {
20         this.id = id;
21     }
22 
23     public String getName() {
24         return name;
25     }
26     public void setName(String name) {
27         this.name = name;
28     }
31 }

 

 1         
 2         HashSet haSe = new HashSet();
 3         
 4         //相同id與name的添加兩次
 5         haSe.add(new Person(1001, "latiny"));
 6         haSe.add(new Person(1001, "latiny"));
 7         
 8         //遍歷HashSet
 9         for(Object obj:haSe)
10         {
11             Person p = (Person)obj;
12             System.out.println(p.getId()+" "+p.getName()+" "+p.hashCode());
13         }
14         

  輸出結果:

1001 latiny 355165777
1001 latiny 1807500377

可以看到name與id相同的兩個對象都成功的加入到了HashSet集合,因為它們的hashCode值不一樣,這顯然沒有達到預期的效果,如果要實現預期的效果需要重寫Person類 equals() 與 hashCode() 方法。

 

② 重寫Person類equals() 與 hashCode() 方法

在上面的Person類添加如下代碼:

 1     //重寫hashCode() 方法
 2     public int hashCode() {
 3         int hash = 7;
 4         hash = hash*31 + this.id*11 + this.name.hashCode()*31;
 5         return hash;
 6     }
 7 
 8     //重寫equals() 方法
 9     public boolean equals(Object obj) {
10         if(this==obj)
11         {
12             return true;
13         }
14         //如果兩個對象的id與name都相等,則兩個對象相等
15         if(obj.getClass()==Person.class)
16         {
17             Person p=(Person)obj;
18             return this.id==p.id && this.name==p.name;
19         }
20         else
21         {
22             return false;
23         }
24     }

  

  重新執行代碼:

 1         
 2         HashSet haSe = new HashSet();
 3         
 4         //相同id與name的對象添加兩次
 5         haSe.add(new Person(1001, "latiny"));
 6         System.out.println(haSe.add(new Person(1001, "latiny")));
 7         
 8         //遍歷HashSet
 9         for(Object obj:haSe)
10         {
11             Person p = (Person)obj;
12             System.out.println(p.getId()+" "+p.getName()+" "+p.hashCode());
13         }
14         

 

  輸出結果: 

false
1001 latiny -46445433

可以看到 id與 name 相同的兩個對象只有一個加入到了HashSet集合,第二次添加時失敗因為我們重寫了equals() 與 hashCode() 方法,只要Person類對象id與name都相同,equals()返回true,hashCode() 返回值相同,HashSet集合就會將這兩個對象視為同一個對象。

 

HashMap 的使用與HashSet類似,作為key的對象也需要重寫 equals() 與 hashCode() 方法,不然也會出現將相同對象作為key值成功插入到HashMap中。

① 未重寫 equals() 與 hashCode() 方法

 1 class Person
 2 {
 3     private String name;
 4     private int id; 
 5     
 6     public Person()
 7     {}
 8     
 9     public Person(int id,String name)
10     {
11         this.id = id;
12         this.name = name;
13     }
14     
15     public int getId() {
16         return id;
17     }
18 
19     public void setId(int id) {
20         this.id = id;
21     }
22 
23     public String getName() {
24         return name;
25     }
26     public void setName(String name) {
27         this.name = name;
28     }
29 
30 }

  

  將Person類作為HashMap的key

 1         
 2         HashMap<Person, String> hash = new HashMap<Person, String>();
 3         hash.put(new Person(1, "latiny"), "周瑜");
 4         hash.put(new Person(1, "latiny"), "曹操");
 5         hash.put(new Person(1, "latiny"), "劉禪");
 6         
 7         //foreach遍歷HashMap
 8         for(Object key:hash.keySet())
 9         {
10             String name = hash.get(key);
11             System.out.println(key+"-->"+name+" "+key.hashCode());
12         }
13         
14         Object obj = hash.get(new Person(1, "latiny"));
15         String name = (String) obj;
16         System.out.println(name);

  輸出結果:

com.latiny.set.Person@544a5ab2-->劉禪 1414159026
com.latiny.set.Person@152b6651-->曹操 355165777
com.latiny.set.Person@6bbc4459-->周瑜 1807500377
null

可以看到,id 與 name 相同的Person對象作為 key 重復添加到HashMap集合中。

更為嚴重的問題是,當我們想要以相同的 id 與 name的Person對象作為key去獲取value 時,結果竟然是null,為什么呢?因為這個Person對象的id 與 name 與之前的三個對象相同,但是在內存中它卻是一個新的對象,有自己獨立的空間,即有自己獨立的hashCode值,由於我們沒有重寫hashCode方法,它的hashCode計算方法還是按照Object這個類來實現的。

 

② 重寫 equals() 與 hashCode() 方法之后

 1 class Person
 2 {
 3     private String name;
 4     private int id; 
 5     
 6     public Person()
 7     {}
 8     
 9     public Person(int id,String name)
10     {
11         this.id = id;
12         this.name = name;
13     }
14     
15     public int getId() {
16         return id;
17     }
18 
19     public void setId(int id) {
20         this.id = id;
21     }
22 
23     public String getName() {
24         return name;
25     }
26     public void setName(String name) {
27         this.name = name;
28     }
29 
30     
31     //重寫hashCode() 方法
32     public int hashCode() {
33         int hash = 7;
34         hash = hash*31 + this.id*11 + this.name.hashCode()*31;
35         return hash;
36     }
37 
38     //重寫equals() 方法
39     public boolean equals(Object obj) {
40         if(this==obj)
41         {
42             return true;
43         }
44         //如果兩個對象的id與name都相等,則兩個對象相等
45         if(obj.getClass()==Person.class)
46         {
47             Person p=(Person)obj;
48             return this.id==p.id && this.name==p.name;
49         }
50         else
51         {
52             return false;
53         }
54     }
55     
56 }

 

  執行測試代碼

 1         
 2         HashMap<Person, String> hash = new HashMap<Person, String>();
 3         hash.put(new Person(1, "latiny"), "周瑜");
 4         hash.put(new Person(1, "latiny"), "曹操");
 5         hash.put(new Person(1, "latiny"), "劉禪");
 6         
 7         //foreach遍歷HashMap
 8         for(Object key:hash.keySet())
 9         {
10             String name = hash.get(key);
11             System.out.println(key+"-->"+name+" "+key.hashCode());
12         }
13         
14         Object obj = hash.get(new Person(1, "latiny"));
15         String name = (String) obj;
16         System.out.println(name);
17         

  輸出結果:

com.latiny.set.Person@fd3b218f-->劉禪 -46456433
劉禪

可以看到,最后一次添加的元素將之前添加的都覆蓋了,因為我們重寫的方法判斷如果Person類的id與name相同,equals()返回true, hashCode() 返回相同的值,HashMap認定它們key值相同,會將之前加入的元素覆蓋。我們也可以將具有相同的id與name的Person類作為key值去訪問value。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM