A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© Jim-剣◆﹏ 高级黑马   /  2013-11-2 23:42  /  1286 人查看  /  5 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 Jim-剣◆﹏ 于 2013-11-3 23:45 编辑

今天看张孝祥老师的高新技术视频时,说完反射,接着视频时《ArrayList_HashSet的比较及Hashcode分析》
说到hashSet与hashCode的问题,老师举了个例子是
  1. <p>import java.io.FileInputStream;
  2. import java.io.InputStream;
  3. import java.util.ArrayList;
  4. import java.util.Collection;
  5. import java.util.HashSet;
  6. import java.util.Properties;</p><p>public class ReflectTest2 {</p><p> /**
  7.   * @param args
  8.   */
  9. public static void main(String[] args) throws Exception{</p><p> </p><p>Collection collections = new HashSet();
  10.         ReflectPoint pt1 = new ReflectPoint(3,3);
  11.         ReflectPoint pt2 = new ReflectPoint(5,5);
  12.         ReflectPoint pt3 = new ReflectPoint(3,3);

  13.         collections.add(pt1);
  14.         collections.add(pt2);
  15.         collections.add(pt3);
  16.         collections.add(pt1);

  17.         pt1.y = 7;
  18.         collections.remove(pt1);

  19.         System.out.println(collections.size());</p><p>}</p><p>}</p>
复制代码
  1. public class ReflectPoint {
  2.         private Date birthday = new Date();
  3.         
  4.         private int x;
  5.         public int y;
  6.         public String str1 = "ball";
  7.         public String str2 = "basketball";
  8.         public String str3 = "itcast";
  9.         
  10.         public ReflectPoint(int x, int y) {
  11.                 super();
  12.                 this.x = x;
  13.                 this.y = y;
  14.         }
  15.         
  16.         
  17.         @Override
  18.         public int hashCode() {
  19.                 final int prime = 31;
  20.                 int result = 1;
  21.                 result = prime * result + x;
  22.                 result = prime * result + y;
  23.                 return result;
  24.         }


  25.         <a>/*@Override</a>
  26.         public boolean equals(Object obj) {
  27.                 if (this == obj)
  28.                         return true;
  29.                 if (obj == null)
  30.                         return false;
  31.                 if (getClass() != obj.getClass())
  32.                         return false;
  33.                 final ReflectPoint other = (ReflectPoint) obj;
  34.                 if (x != other.x)
  35.                         return false;
  36.                 if (y != other.y)
  37.                         return false;
  38.                 return true;
  39.         }*/


  40.         @Override
  41.         public String toString(){
  42.                 return str1 + ":" + str2 + ":" + str3;
  43.         }


  44.         public int getX() {
  45.                 return x;
  46.         }


  47.         public void setX(int x) {
  48.                 this.x = x;
  49.         }


  50.         public int getY() {
  51.                 return y;
  52.         }


  53.         public void setY(int y) {
  54.                 this.y = y;
  55.         }


  56.         public Date getBirthday() {
  57.                 return birthday;
  58.         }


  59.         public void setBirthday(Date birthday) {
  60.                 this.birthday = birthday;
  61.         }
  62.         
  63. }
复制代码
老师说,对象存进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()方法javahashCode()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)

QQ截图20131103233714.png

评分

参与人数 1技术分 +1 收起 理由
To + 1 很给力!

查看全部评分

5 个回复

倒序浏览
楼主的想法感觉有点本末倒置,复写hascode和equals方法就是为了排除我们认为相同的元素,保证元素在集合中的唯一性,还有哦,我把equals方法注释掉也不可以删除的。
回复 使用道具 举报
以前回答过别人这样的问题,我给你在解说一下吧:

为什么要重写两个方法呢:
equals方法判断出两个对象相同,那么它们的hashCode一定相同
equals方法判断出两个对象不同,那么它们的hashCode可能相同,也可能不同的
因为hashCode方法是根据一定的算法实现的,所以会存在两个不相同的对象算出的hashCode是相同的!
反过来:
hashCode相同的两个对象,equals一定相同
hashCode不相同的两个对象,equals可能相同,也可能不同哦!


在java的集合中,判断两个对象是否相等的规则是:
1),判断两个对象的hashCode是否相等
      如果不相等,认为两个对象也不相等,完毕
      如果相等,转入2)
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。后面会重点讲到这个问题。)
2),判断两个对象用equals运算是否相等
      如果不相等,认为两个对象也不相等
      如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

评分

参与人数 1技术分 +1 收起 理由
乔兵 + 1

查看全部评分

回复 使用道具 举报
寻丶丶觅 发表于 2013-11-3 15:51
楼主的想法感觉有点本末倒置,复写hascode和equals方法就是为了排除我们认为相同的元素,保证元素在集合中 ...

谢谢你回答,我昨晚发问题仓促了写,有些表达错了,现在跟新了,而且我也总结了答案在下面,你可以看一下
回复 使用道具 举报
~﹏~ 发表于 2013-11-3 16:54
以前回答过别人这样的问题,我给你在解说一下吧:

为什么要重写两个方法呢:

谢谢你的回答,我昨晚的问题说得有点错误,现在更新了,而且我也总结了答案在楼下可以看看,大家交流一下
回复 使用道具 举报
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()方法javahashCode()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中删除对象,最终导致内存泄露
哈希表存储方式:
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马