本帖最后由 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中删除对象,最终导致内存泄露
|