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。