黑马程序员-hashSet的存储、检索与hashCode和equals的问题

---------------------- 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()方法

javahashCode()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中删除对象,最终导致内存泄露

 

 

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------
详细请查看: http://edu.csdn.net 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值