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 plain copy


程序中添加了两只白色的小狗到集合dogSet中. 且 size()方法显示有2只白色的小狗.但为什么用 contains()方法来判断时却提示没有白色的小狗呢?

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. new HashSet<Dog>();  
14. new Dog("white"));  
15. new Dog("white"));  
16.    
17. "We have " + dogSet.size() + " white dogs!");  
18.    
19. if(dogSet.contains(new Dog("white"))){  
20. "We have a white dog!");  
21. else{  
22. "No white dog!");  
23.         }     
24.     }  
25. }  
 

上述代码的输出为: 

 
   [plain]  
   view plain 
   copy 
   
 
   
 
 
1. We have 2 white dogs!  
2. No white dog!



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 plain 
   copy 
   
 
   
 
 
1. HashSet<Dog> a = new HashSet<Dog>();  
2. a.add(null);  
3. if(a.contains(null)){  
4. "true");  
5. }



Java的根类Object定义了  public boolean equals(Object obj) 方法.因此所有的对象,包括数组(array,[]),都实现了此方法。


在自定义类里,如果没有明确地重写(override)此方法,那么就会使用Object类的默认实现.即只有两个对象(引用)指向同一块内存地址(即同一个实际对象, x==y为true)时,才会返回true。


如果把Dog类修改为如下代码,能实现我们的目标吗?

[java]  
   view plain 
   copy 
   
 
   
 
 
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 plain 
   copy 
   
 
   
 
 
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 plain 
   copy 
   
 
   
 
 
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. new HashSet<Person>();  
39. //  
40. new Person("tiemao");  
41.         persons.add(person);  
42. // 修改name, 则依赖hash的集合可能失去作用  
43. "ren");  
44. // 同一个对象,居然是false,原因是我们重写了hashCode,打破了hashCode不变的基本约定  
45. boolean has = persons.contains(person);  
46. int size = persons.size();  
47. "has="+has); // has=false.  
48. "size="+size);// size=1  
49.     }  
50. }



参考文章: 


http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html