黑马程序员技术交流社区
标题: hashSet与hashCode的问题 [打印本页]
作者: Jim-剣◆﹏ 时间: 2013-11-2 23:42
标题: hashSet与hashCode的问题
本帖最后由 Jim-剣◆﹏ 于 2013-11-3 23:45 编辑
今天看张孝祥老师的高新技术视频时,说完反射,接着视频时《ArrayList_HashSet的比较及Hashcode分析》
说到hashSet与hashCode的问题,老师举了个例子是- <p>import java.io.FileInputStream;
- import java.io.InputStream;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.HashSet;
- import java.util.Properties;</p><p>public class ReflectTest2 {</p><p> /**
- * @param args
- */
- public static void main(String[] args) throws Exception{</p><p> </p><p>Collection collections = new HashSet();
- ReflectPoint pt1 = new ReflectPoint(3,3);
- ReflectPoint pt2 = new ReflectPoint(5,5);
- ReflectPoint pt3 = new ReflectPoint(3,3);
- collections.add(pt1);
- collections.add(pt2);
- collections.add(pt3);
- collections.add(pt1);
- pt1.y = 7;
- collections.remove(pt1);
- System.out.println(collections.size());</p><p>}</p><p>}</p>
复制代码- public class ReflectPoint {
- private Date birthday = new Date();
-
- private int x;
- public int y;
- public String str1 = "ball";
- public String str2 = "basketball";
- public String str3 = "itcast";
-
- public ReflectPoint(int x, int y) {
- super();
- this.x = x;
- this.y = y;
- }
-
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + x;
- result = prime * result + y;
- return result;
- }
- <a>/*@Override</a>
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- final ReflectPoint other = (ReflectPoint) obj;
- if (x != other.x)
- return false;
- if (y != other.y)
- return false;
- return true;
- }*/
- @Override
- public String toString(){
- return str1 + ":" + str2 + ":" + str3;
- }
- public int getX() {
- return x;
- }
- public void setX(int x) {
- this.x = x;
- }
- public int getY() {
- return y;
- }
- public void setY(int y) {
- this.y = y;
- }
- public Date getBirthday() {
- return birthday;
- }
- public void setBirthday(Date birthday) {
- this.birthday = birthday;
- }
-
- }
复制代码 老师说,对象存进hashSet中的时候,会根据对象的hashCode(),寻找在哈希表里的存储区域
最好不要修改被用来关联计算hashCode()的成员变量的值,如果修改了,很可能会查找,删除失败(这里说的是可能)
因为对象存进hash表的时候,那么该对象所对应的哈希表的存储区域就固定了,如果此时修改了对象中关联hashCode()的值,那么查找的时候,就会按照新的hashCode()的值去哈希表中查找对象
那么就可能会找不到原来的对象了,可能会导致内存溢出的问题
的确,按照老师的程序运行,令pt1.y = 7; 之后,再删除pt1就失败了
主要的问题是:老师讨论的前提是对象的hashCode()覆写了而equal()没有覆写,一直删除失败
经过试验,hashCode()和equal()两者同时都覆写了的情况下,有时候会导致删除失败,有时会删除成功,这是为什么?
我的答案:
hashSet是根据对象的hashCode()值来决定对象在集合中的存储位置
在覆写hashCode()所用的成员的值最好不要再改变,否者会删除对象的时候,可能会失败
原因:此时的讨论默认同时覆写了hashCode()和equals()
因为对象在存储进集合的时候,hashCode()值已经定了,也就是说存储的区域已经定下来,假设修改前hashCode()值指向区域为A,如果此时修改了计算hashCode()所引用成员的值,那么在删除操作时候,虚拟机会根据新值重新计算hashCode(),去查找该值属于哪个区域,然后再取出该区域的所有对象,与对象用equal()进行比较,此时有两种情况
(1) 如果新的hashCode值指向的区域指向了C,此时java会用equal()与C区域内的所有对象进行比较,由于覆写了equal(),所以没有对象符合,此时删除失败
(2) 如果新的hashCode值指向的区域恰巧还是指向A区域,那么用equal()与A区域中的对象比较,则可以找到原来的对象,此时删除成功
这样就解释了为什么会有可能会失败
删除失败,这种积累效应会导致内存泄露,因为对象堆积越来越多,占用内存空间,未能被释放,那么就会内存泄露
上述讨论是基于同时覆写了hashCode()和equal()方法,java对hashCode()于equal()的覆写有常规协定,必须同时覆写这两个方法,该协定声明相等对象必须具有相等的哈希码
那么此时如果没有覆写equal()的方法,会发生什么情况?
没有覆写equals(),则子类对象中equals()则是继承Object类的
此时equals()比较的是对象的地址值,地址值引用的是hashCode()值(由于多态,子类覆写了hashCode(),那么父类的equals()将会引用子类的hashCode())
那么同样会有两种情况
(1) 如果新的hashCode值指向的区域指向了C,此时java会用equal()与C区域内的所有对象进行比较,不同对象的内存地址值肯定是不一样的,那么肯定没有对象符合,此时删除失败
(2) 如果新的hashCode值指向的区域恰巧还是指向A区域,那么修改后的对象equals()是继承其父类,并且equals()内部比较的值引用的是子类的hashCode(),那么此时原来的对象和修改后的对象,在没有覆写equals()的时候,用equals()进行比较,还是不相等,必定找不到对象,删除失败
以上就解释了为什么一般修改了equal()最好顺带修改hashCode()值,为了保证用equals()比较相等的对象必定具有相同hashCode值,但反之不成立,即用equals()比较不相等的对象,可以有相同的hashCode值,或者说hashCode值相同的两个对象用equals()比较可以不等
附:其实只有当类的实例对象在要采用hash算法进行存储和检索的时候,才需要重写hashCode(),但是如果不是采用hash算法进行存储和检索或者暂时用不上,但是为了规范,建议覆写equals()的同时覆写hashCode(),为以后的程序拓展提供便利
结论:当一个对象被存储进hashSet集合当中的时候,就不要去改变这个对象中参与hashCode值计算的字段了,否则,对象修改后的哈希值与最初存储进集合时的哈希值就不一样了,在这种情况下,即使在contains()方法使用该对象的当前引用作为参数去hashSet检索对象,也将返回找不到对象的结果,这也会导致无法单独从hashSet中删除对象,最终导致内存泄露
-
QQ截图20131103233714.png
(49.28 KB, 下载次数: 0)
作者: 寻丶丶觅 时间: 2013-11-3 15:51
楼主的想法感觉有点本末倒置,复写hascode和equals方法就是为了排除我们认为相同的元素,保证元素在集合中的唯一性,还有哦,我把equals方法注释掉也不可以删除的。
作者: ~﹏~ 时间: 2013-11-3 16:54
以前回答过别人这样的问题,我给你在解说一下吧:
为什么要重写两个方法呢:
equals方法判断出两个对象相同,那么它们的hashCode一定相同
equals方法判断出两个对象不同,那么它们的hashCode可能相同,也可能不同的
因为hashCode方法是根据一定的算法实现的,所以会存在两个不相同的对象算出的hashCode是相同的!
反过来:
hashCode相同的两个对象,equals一定相同
hashCode不相同的两个对象,equals可能相同,也可能不同哦!
在java的集合中,判断两个对象是否相等的规则是:
1),判断两个对象的hashCode是否相等
如果不相等,认为两个对象也不相等,完毕
如果相等,转入2)
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。后面会重点讲到这个问题。)
2),判断两个对象用equals运算是否相等
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
作者: Jim-剣◆﹏ 时间: 2013-11-3 23:33
谢谢你回答,我昨晚发问题仓促了写,有些表达错了,现在跟新了,而且我也总结了答案在下面,你可以看一下
作者: Jim-剣◆﹏ 时间: 2013-11-3 23:34
谢谢你的回答,我昨晚的问题说得有点错误,现在更新了,而且我也总结了答案在楼下可以看看,大家交流一下
作者: Jim-剣◆﹏ 时间: 2013-11-3 23:37
hashSet是根据对象的hashCode()值来决定对象在集合中的存储位置
在覆写hashCode()所用的成员的值最好不要再改变,否者会删除对象的时候,可能会失败
此时的讨论默认同时覆写了hashCode()和equals()
原因:
因为对象在存储进集合的时候,hashCode()值已经定了,也就是说存储的区域已经定下来,假设修改前hashCode()值指向区域为A,如果此时修改了计算hashCode()所引用成员的值,那么在删除操作时候,虚拟机会根据新值重新计算hashCode(),去查找该值属于哪个区域,然后再取出该区域的所有对象,与对象用equal()进行比较,此时有两种情况
(1) 如果新的hashCode值指向的区域指向了C,此时java会用equal()与C区域内的所有对象进行比较,由于覆写了equal(),所以没有对象符合,此时删除失败
(2) 如果新的hashCode值指向的区域恰巧还是指向A区域,那么用equal()与A区域中的对象比较,则可以找到原来的对象,此时删除成功
这样就解释了为什么会有可能会失败
删除失败,这种积累效应会导致内存泄露,因为对象堆积越来越多,占用内存空间,未能被释放,那么就会内存泄露
上述讨论是基于同时覆写了hashCode()和equal()方法,java对hashCode()于equal()的覆写有常规协定,必须同时覆写这两个方法,该协定声明相等对象必须具有相等的哈希码
那么此时如果没有覆写equal()的方法,会发生什么情况?
没有覆写equals(),则子类对象中equals()则是继承Object类的
此时equals()比较的是对象的地址值,地址值引用的是hashCode()值(由于多态,子类覆写了hashCode(),那么父类的equals()将会引用子类的hashCode())
那么同样会有两种情况
(1) 如果新的hashCode值指向的区域指向了C,此时java会用equal()与C区域内的所有对象进行比较,不同对象的内存地址值肯定是不一样的,那么肯定没有对象符合,此时删除失败
(2) 如果新的hashCode值指向的区域恰巧还是指向A区域,那么修改后的对象equals()是继承其父类,并且equals()内部比较的值引用的是子类的hashCode(),那么此时原来的对象和修改后的对象,在没有覆写equals()的时候,用equals()进行比较,还是不相等,必定找不到对象,删除失败
以上就解释了为什么一般修改了equal()最好顺带修改hashCode()值,为了保证用equals()比较相等的对象必定具有相同hashCode值,但反之不成立,即用equals()比较不相等的对象,可以有相同的hashCode值,或者说hashCode值相同的两个对象用equals()比较可以不等
附:其实只有当类的实例对象在要采用hash算法进行存储和检索的时候,才需要重写hashCode(),但是如果不是采用hash算法进行存储和检索或者暂时用不上,但是为了规范,建议覆写equals()的同时覆写hashCode(),为以后的程序拓展提供便利
结论:当一个对象被存储进hashSet集合当中的时候,就不要去改变这个对象中参与hashCode值计算的字段了,否则,对象修改后的哈希值与最初存储进集合时的哈希值就不一样了,在这种情况下,即使在contains()方法使用该对象的当前引用作为参数去hashSet检索对象,也将返回找不到对象的结果,这也会导致无法单独从hashSet中删除对象,最终导致内存泄露
哈希表存储方式:
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |