---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object obj) {// 覆写equals方法
return this.name.equals(obj.name) && this.age == obj.age;
}
public int hashCode() {// 覆写hashCode方法
return this.name.hashCode() + age;
}
}
class Demo {
public static void main(String args[]) {
Person per1 = new Person("zhangsan", 20);
Person per2 = new Person("lisi", 30);
Person per3 = new Person("zhangsan", 20);
HashSet<Person> hs = new HashSet<Person>();
hs.add(per1);
hs.add(per2);
hs.add(per3);
System.out.println(hs.size());// 输出结果为:2
per2.age = 50;// 修改per2的与计算hashCode值相关联的值
hs.remove(per2);// 尝试删除修改后的per2
System.out.println(hs.size());// 此时输出结果任然为:2,也就是说,删除失败
}
}
问题:为什么删除失败?
解答:
hashSet是根据对象的hashCode()值来决定对象在集合中的存储位置
在覆写hashCode()所用的成员的值最好不要再改变,否者会删除对象的时候,可能会失败
此时的讨论默认同时覆写了hashCode()和equals()
原因:
哈希表存储方式:
因为对象在存储进集合的时候,hashCode()值已经定了,也就是说存储的区域已经定下来,假设修改前hashCode()值指向区域为区域1,如果此时修改了计算hashCode()所引用成员的值,那么在删除操作时候,虚拟机会根据新值重新计算hashCode(),去查找该值属于哪个区域,然后再取出该区域的所有对象,与对象用equals()进行比较,此时有两种情况
(1) 如果新的hashCode值指向的区域指向了区域2,此时java会用equals()与区域2内的所有对象进行比较,由于覆写了equals(),有可能会有对象符合(看你如何实现equals,但几率太小),但是,该符合的对象并不是用户原本要查找的目标对象,此时的删除成功但是属于误删除(equals相等但hashCode不等造成),也有可能没有对象符合,此时删除失败
(2) 如果新的hashCode值指向的区域恰巧还是指向区域1,那么用equal()与区域1中的对象比较,则可以找到原来的对象,此时删除成功
这样就解释了为什么会有可能会失败
删除失败,这种积累效应会导致内存泄露,因为对象堆积越来越多,占用内存空间,未能被释放,那么就会内存泄露
上述讨论是基于同时覆写了hashCode()和equal()方法
java对hashCode()于equals()的覆写有常规协定,必须同时覆写这两个方法,该协定声明相等对象必须具有相等的哈希码
那么此时如果没有覆写equal()的方法,会发生什么情况?
没有覆写equals(),则子类对象中equals()则是继承Object类的
此时equals()比较的是对象的地址值,地址值引用的是hashCode()值(由于多态,子类覆写了hashCode(),那么父类的equals()将会引用子类的hashCode())
那么同样会有两种情况
(1) 如果新的hashCode值指向的区域指向了区域2,此时java会用equals()与区域2内的所有对象进行比较,不同对象的内存地址值肯定是不一样的,那么肯定没有对象符合,此时删除失败
(2) 如果新的hashCode值指向的区域恰巧还是指向区域1,那么修改后的对象equals()是继承其父类,并且equals()内部比较的值引用的是子类的hashCode(),那么此时原来的对象和修改后的对象,在没有覆写equals()的时候,用equals()进行比较,还是不相等,必定找不到对象,删除失败
以上就解释了为什么一般修改了equals()最好顺带修改hashCode()值,为了保证用equals()比较相等的对象必定具有相同hashCode值,但反之不成立,即用equals()比较不相等的对象,可以有相同的hashCode值,或者说hashCode值相同的两个对象用equals()比较可以不等(这里就能解释上述第一种情况的误删除,误删除是因为存在equals()相等但hashCode值却不等,为了避免误删除,所以最好遵守这个规范)
附:其实只有当类的实例对象在要采用hash算法进行存储和检索的时候,才需要重写hashCode(),但是如果不是采用hash算法进行存储和检索或者暂时用不上,但是为了规范,建议覆写equals()的同时覆写hashCode(),为以后的程序拓展提供便利
结论:
当一个对象被存储进hashSet集合当中的时候,就不要去改变这个对象中参与hashCode值计算的字段了,否则,对象修改后的哈希值与最初存储进集合时的哈希值就不一样了,在这种情况下,即使在contains()方法使用该对象的当前引用作为参数去hashSet检索对象,将可能返回找不到目标对象的结果,这也会导致无法单独从hashSet中删除对象,最终导致内存泄露