为什么覆写equals()方法的时候总是要覆写hashcode()?

要回答这个问题,我们应该先认识一下obj中的equals和hascode方法

1.equals()方法在obj中定义如下:

public boolean equals(Object obj) {
return (this == obj);

可以看到因为‘==’,所以是将两个对象的地址值进行比较(比较对象的引用是不是相同),但是在String和Integer等包装类中已经重写了equals()和hashcode()方法,比如在String类中:

public boolean equals(Object anObject) {
if (this == anObject) {
    return true;
}
if (anObject instanceof String) {
    String anotherString = (String)anObject;
    int n = count;
    if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
    if (v1[i++] != v2[j++])
return false;
}
return true;
    }
}
return false;

可以看出这不是对象地址的比较了,而是内容的比较,依此类推,其他包装类也是这样的

附加:重写equals()需要注意的地方:

• 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
• 反射性:x.equals(x)必须返回是“true”。
• 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
• 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
• 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

 2.hashcode()方法在obj中定义如下:

public native int hashCode(); 

本地方法,与机器有关,但是它是将一个对象的内部地址转化成一个整数来实现的,在String等包装类中,hascode()方法也被重写了,例如String的Hascode():

public int hashCode() {
int h = hash;
if (h == 0) {
    int off = offset;
    char val[] = value;
    int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;

可以看到String的hashcode()与对象的内容有关了,与地址无关了

简单总结一下:在obj中的equals()和hashcode()是原始的,没有被重写的,且二者都与对象的地址有关,在String等包装类中,equals()和hashcode()是被重写了的,与对象的内容有关

3.先看一下JDK中对hashcode的常协规定:

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能

        总结一下:根据equals()对象相同的时候,hashcode()一定要相同,根据equals()对象不同的时候,hashcode()可以相同,可以不同

好了,既然两个方法我们都了解的差不多了,那么”为什么覆写equals()方法的时候总是要覆写hashcode()“???

首先,我认为hascode主要是为集合类服务的,下面以hashset为例子,那我们先来了解一下HashSet

HashSet:不允许出现重复对象,元素存放位置不确定

HashSet存放元素的原理:当向Hashset中增加元素的时候,首先计算元素的hashcode,根据hashcode得到一个位置来存放该元素,如果在该位置没有元素,那么集合认为该元素不存在与计划中,所以直接增加进去,如果该位置有一个元素,那么将要放进去的元素与该位置上面已经存在的元素进行equals比较,若返回flase,则集合认为该对象不在集合中,可加入,然后因为该位置已经存在元素,所以进行一次散列,得到一个新的地址,若得到的地址中还是存在元素,那么一直进行散列,直到找到的地址中没有元素为止,然后把需要增加的元素放入该地址中,若true,则集合认为该元素已经存在与集合中,不会将对象增加到集合中。

现在我们对HashSet有了一定的了解,那么继续”为什么覆写equals()方法的时候总是要覆写hashcode()“这个问题

假如我在构造一个类的时候,只重写了equals方法而没有重写hashcode()方法,在我不需要使用集合的时候可能看不出什么问题,但是一旦我需要使用集合,问题就大了,而且这个bug还很隐秘,举个栗子:

 1 import java.util.*; 
 2 public class HashSetTest 
 3 { 
 4    public static void main(String[] args) 
 5     { 
 6                  HashSet hs=new HashSet(); 
 7                  hs.add(new Student(1,"zhangsan")); 
 8                  hs.add(new Student(2,"lisi")); 
 9                  hs.add(new Student(3,"wangwu")); 
10                  hs.add(new Student(1,"zhangsan")); 
11   
12                  Iterator it=hs.iterator(); 
13                  while(it.hasNext()) 
14                  { 
15                         System.out.println(it.next()); 
16                  } 
17      } 
18 } 
19 class Student 
20    { 
21      int num; 
22      String name; 
23      Student(int num,String name) 
24                 { 
25                 this.num=num; 
26                  this.name=name; 
27                  } 
28               public String toString() 
29                 { 
30                     return num+":"+name; 
31                  } 
32            }      

输出结果:(结果输出序号无序的原因是Hashset无序储存)

1:zhangsan

1:zhangsan 

 3:wangwu 

 2:lisi 

观察结果,我们使用的是HashSet,但是结果中出现了相同的元素,这是为什么呢?我们来观察一下,我只重写了equals(),重写的equals比较的是对象的内容,当有两个new Student(1,"zhangsan"))的时候,这是两个内容相同的不同地址的对象,使用equals比较的结果是True,当时我没有重写hashcode,而obj下的hashcode的取值与对象的地址有关,所以这两个对象的hashcode是不同的,因为他们的地址不同,所以在放入集合的时候,集合看到二者的hashcode不同,认为二者是两个不同的对象,所以集合中就有了两个重复的对象,分析完毕

解决方法:重写hascode()方法,继续上面的栗子:

 1 class Student 
 2 { 
 3 int num; 
 4 String name; 
 5 Student(int num,String name) 
 6 { 
 7             this.num=num; 
 8             this.name=name; 
 9 } 
10 public int hashCode() 
11 { 
12             return num*name.hashCode(); 
13 } 
14 public boolean equals(Object o) 
15 { 
16             Student s=(Student)o; 
17             return num==s.num && name.equals(s.name); 
18 } 
19 public String toString() 
20 { 
21             return num+":"+name; 
22 } 
23 } 

结果:(结果输出序号无序的原因是Hashset无序储存)

1:zhangsan
3:wangwu
2:lisi 

可以看到输出结果中已经没有重复的元素了,这是因为我重写了hascode()方法,使得hashcode的取值只与对象的内容有关,而与对象的地址无关,所以集合比较hascode的时候看到二者的hascode相等,会认为二者是相同的对象,所以重复的对象不会被放进集合中

错误和不清楚的地方还请大家指出,我也是初学者,难免有错误的地方,欢迎大家一起讨论学习!!!!

欢迎拍砖!!!!!

                                                                                                                                                                                                                                                                                                 --期末,下午,16:58

 

posted @ 2017-12-25 17:00  西*风  阅读(893)  评论(1编辑  收藏  举报