Java中Set的contains()方法

轉自http://blog.csdn.net/renfufei/article/details/14163329

Java中Set的contains()方法 —— hashCode與equals方法的約定及重寫原則


翻譯人員: 鐵錨
翻譯時間: 2013年11月5日
原文鏈接: Java hashCode() and equals() Contract for the contains(Object o) Method of Set

本文主要討論 集合Set 中存儲對象的 hashCode 與 equals 方法應遵循的約束關係.

新手對Set中contains()方法的疑惑
[java] view plaincopy
  1. import java.util.HashSet;  
  2.    
  3. class Dog{  
  4.     String color;  
  5.    
  6.     public Dog(String s){  
  7.         color = s;  
  8.     }     
  9. }  
  10.    
  11. public class SetAndHashCode {  
  12.     public static void main(String[] args) {  
  13.         HashSet<Dog> dogSet = new HashSet<Dog>();  
  14.         dogSet.add(new Dog("white"));  
  15.         dogSet.add(new Dog("white"));  
  16.    
  17.         System.out.println("We have " + dogSet.size() + " white dogs!");  
  18.    
  19.         if(dogSet.contains(new Dog("white"))){  
  20.             System.out.println("We have a white dog!");  
  21.         }else{  
  22.             System.out.println("No white dog!");  
  23.         }     
  24.     }  
  25. }  

上述代碼的輸出爲:
[plain] view plaincopy
  1. We have 2 white dogs!  
  2. No white dog!  

程序中添加了兩隻白色的小狗到集合dogSet中. 且 size()方法顯示有2只白色的小狗.但爲什麼用 contains()方法來判斷時卻提示沒有白色的小狗呢?

Set的contains(Object o) 方法詳解
Java的API文檔指出: 當且僅當 本set包含一個元素 e,並且滿足(o==null ? e==null : o.equals(e))條件時,contains()方法才返回true. 因此 contains()方法 必定使用equals方法來檢查是否相等.
需要注意的是: set 中是可以包含 null值的(常見的集合類都可以包含null值). 所以如果添加了null,然後判斷是否包含null,將會返回true,代碼如下所示:
[java] view plaincopy
  1. HashSet<Dog> a = new HashSet<Dog>();  
  2. a.add(null);  
  3. if(a.contains(null)){  
  4.     System.out.println("true");  
  5. }  

Java的根類Object定義了  public boolean equals(Object obj) 方法.因此所有的對象,包括數組(array,[]),都實現了此方法。
在自定義類裏,如果沒有明確地重寫(override)此方法,那麼就會使用Object類的默認實現.即只有兩個對象(引用)指向同一塊內存地址(即同一個實際對象, x==y爲true)時,纔會返回true。
如果把Dog類修改爲如下代碼,能實現我們的目標嗎?
[java] view plaincopy
  1. class Dog{  
  2.     String color;  
  3.    
  4.     public Dog(String s){  
  5.         color = s;  
  6.     }  
  7.    
  8.     //重寫equals方法, 最佳實踐就是如下這種判斷順序:  
  9.     public boolean equals(Object obj) {  
  10.         if (!(obj instanceof Dog))  
  11.             return false;     
  12.         if (obj == this)  
  13.             return true;  
  14.         return this.color == ((Dog) obj).color;  
  15.     }  
  16.    
  17. }  
英文答案是: no.

問題的關鍵在於 Java中hashCode與equals方法的緊密聯繫. hashCode() 是Object類定義的另一個基礎方法.

equals()與hashCode()方法之間的設計實現原則爲:
如果兩個對象相等(使用equals()方法),那麼必須擁有相同的哈希碼(使用hashCode()方法).
即使兩個對象有相同的哈希值(hash code),他們不一定相等.意思就是: 多個不同的對象,可以返回同一個hash值.

hashCode()的默認實現是爲不同的對象返回不同的整數.有一個設計原則是,hashCode對於同一個對象,不管內部怎麼改變,應該都返回相同的整數值.
在上面的例子中,因爲未定義自己的hashCode()實現,因此默認實現對兩個對象返回兩個不同的整數,這種情況破壞了約定原則。

解決辦法
[java] view plaincopy
  1. class Dog{  
  2.     String color;  
  3.    
  4.     public Dog(String s){  
  5.         color = s;  
  6.     }  
  7.    
  8.     //重寫equals方法, 最佳實踐就是如下這種判斷順序:  
  9.     public boolean equals(Object obj) {  
  10.         if (!(obj instanceof Dog))  
  11.             return false;     
  12.         if (obj == this)  
  13.             return true;  
  14.         return this.color == ((Dog) obj).color;  
  15.     }  
  16.    
  17.     public int hashCode(){  
  18.         return color.length();//簡單原則  
  19.     }  
  20. }  


但是上面的hashCode實現,要求Dog的color是不變的.否則會出現如下的這種困惑:
[java] view plaincopy
  1. import java.util.HashSet;  
  2. import java.util.Set;  
  3.   
  4.   
  5. public class TestContains {  
  6.   
  7.   
  8.     public static final class Person{  
  9.         private String name = "";  
  10.         public Person(String n) {  
  11.             setName(n);  
  12.         }  
  13.         public String getName() {  
  14.             return name;  
  15.         }  
  16.         public void setName(String name) {  
  17.             this.name = (name==null)? "" : name;  
  18.         }  
  19.         @Override  
  20.         public int hashCode() {  
  21.             // 請考慮是否值得這麼做,因爲此時name是會變的.  
  22.             return name.length();  
  23.             // 推薦讓name不可改變  
  24.         }  
  25.         @Override  
  26.         public boolean equals(Object obj) {  
  27.             if(!(obj instanceof Person)){  
  28.                 return false;  
  29.             }  
  30.             if(obj == this){  
  31.                 return true;  
  32.             }  
  33.             return this.name.equals(((Person)obj).name);  
  34.         }  
  35.     };  
  36.       
  37.     public static void main(String[] args) {  
  38.         Set<Person> persons = new HashSet<Person>();  
  39.         //  
  40.         Person person = new Person("tiemao");  
  41.         persons.add(person);  
  42.         // 修改name, 則依賴hash的集合可能失去作用  
  43.         person.setName("ren");  
  44.         // 同一個對象,居然是false,原因是我們重寫了hashCode,打破了hashCode不變的基本約定  
  45.         boolean has = persons.contains(person);  
  46.         int size = persons.size();  
  47.         System.out.println("has="+has); // has=false.  
  48.         System.out.println("size="+size);// size=1  
  49.     }  
  50. }  
發佈了42 篇原創文章 · 獲贊 0 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章