重写equals,hashcode原理详解
个人认为已经讲得很清楚了!希望大家有问题多多指教,评论即可!
Object中的原生方法:
Object中:
- hashcode():返回的是对象的地址,所以这种情况下不同对象的hashcode肯定不同
- equals():比较的是对象的地址
问题 hashmap,String,hashset中的hashcode和equals方法到底是怎么重写的!
知识扩展:(非常重要!!!!)
-
1.hashmap和hashset中都是不允许元素重复的,那么我们每次加元素进去的时候,都要进行比较,是否元素重复了
-
2.那么我们是否可以用Object里的equals方法来判断元素是否相同呢?答案是不行的,因为Object中的equals方法是判断对象地址的,如果是两个不同的对象,但是里面的内容相同,通过object中的equals方法同样返回的是不等,那么还会造成重复添加元素的问题,所以这里我们就要有一个结论,必须重写equals方法!(如何重写下面会讲到,无非就是判断内容是否相等)
-
3.在我们重写equals方法后,我们又会发现一个问题。当集合中的数据量过大的时候,我们每次添加元素都要调用成千上万次的equals方法,那么这就使我们的代码效率非常低,所以这就是为什么我们要改写hashcode的原因。
-
4.我们刚刚讲到Object中的hashcode方法是直接返回对象地址的,也就是说这种hashcode方法也无法根据对象的内容生成散列值(也就是方法返回的值),所以我们要改写Object中的hashcode方法,使其根据对象的内容生成一个散列值,每次插入一个新的对象,都要生成一个散列值,将其插入一个table中,那么这个table有什么用呢?
-
5.实际上hashmap,hashset中判断元素是否重复是有两个过程的,首先生成插入新对象的hashcode,到我们刚刚寻找的table中去寻找(这种查询方式很快,用了哈希表的原理),当我们发现没有不同的散列值的时候,就可以判断这是一个不重复的元素,就可以直接插入了。当我们发现table已经有相同的散列值的时候,!!!并不是可以直接判断此对象就是重复元素了,还要调用equals方法,再判断一次,如果判断的结果还是真,那么就真的是重复元素了,就不存了,如果equals判断为假,那么就重新散列其他的值,再进行插入判断。
问题:为什么hashcode方法判断完为重复了,还要让equals再判断一次!
答:实际上hashcode方法(重写之后)的原理仅仅是根据对象内容去返回一串定长的数字,但是当数据量很大的时候,总会出现不同内容生成同样散列值的情况,因为这串数字是定长的!所以这就是关键了。
总结:
- hashcode相同,元素对象不一定重复
- hashcode不同,元素对象一定不重复
- euqals判断是最精确的,用hashcode做第一步的初步判断是为了提高程序效率
String类中的hashcode和equals改写:
//string中的hashcode方法
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
//string类中的equals方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
- String类中的hashcode:根据字符串内容生成的一串数字,也就是说,一般情况下(这里就是指不出现不同元素相同散列值的情况)只要字符串的内容相等,那么这两个String对象的散列值就相同。
- String类的equals:是去判断两个字符串每一个字符是否相等,最精准的判断
向hashset中添加String对象实例:
String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("world");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
System.out.println(str1.hashCode());//99162322
System.out.println(str1.hashCode());//99162322
Set hashset = new HashSet();
hashset.add(str1);
hashset.add(str2);
hashset.add(str3);
System.out.println(hashset);//[world, hello]
我们可以看到,事实上str1和str2的hashcode是一样的,但是hashset并没有重复添加这连个元素,就是因为散列值一样之后还要进行equals方法的判断!
向hashset中添加不重写hashcode,equals的对象:
这里我们自定义对象DataString,里面没有进行重写hashcode,equals方法
DataString dataString1 = new DataString("hello",20);
DataString dataString2 = new DataString("hello",20);
DataString dataString3 = new DataString("world",21);
System.out.println(dataString1.hashCode());//460141958
System.out.println(dataString2.hashCode());//1163157884
System.out.println(dataString3.hashCode());//1956725890
Set hashset1 = new HashSet();
hashset1.add(dataString1);
hashset1.add(dataString2);
hashset1.add(dataString3);
System.out.println(hashset1);
//DataString{name='world', age=21},
//DataString{name='hello', age=20},
//DataString{name='hello', age=20}
这里我们又可以看到,由于没有重写hashcode方法,哪怕内容相同的两个对象,hashcode也是不同的,所以我们可以添加重复对象,而实际上hashset是不允许元素重复的!所以我们要重写hashcode方法和euqals方法!
重写方法:
public int hashCode(){
return age*name.hashCode();
}
public boolean equals(Object o) {
DataString s = (DataString) o;
return age == s.age && name.equals(s.name);
}
当我们重写方法之后再运行刚刚的程序:
结果:[DataString{name=‘hello’, age=20}, DataString{name=‘world’, age=21}]
所以到这基本上就可以明白了!