要回答这个问题,我们应该先认识一下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