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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 刘士林 中级黑马   /  2012-10-30 11:11  /  2814 人查看  /  6 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

public class Test {

public static void main(String[] args) {
  // TODO Auto-generated method stub

  
  
  HashSet hs=new HashSet();
  Person p1=new Person("lili01",18);
  Person p2=new Person("lili02",14);
  hs.add(p1);
  hs.add(p2);
  System.out.println(hs.size());
  p1.age =16;
  hs.remove(p1);
  System.out.println(hs.size());
  
}

}
class Person
{
private String name;
  int age;
Person(String name, int age)
{
  this.name=name;
  this.age =age;
}
  public int hashCode()
  {
   return name.hashCode()+age*17;
  }
}

打印结果如下:
2
2
删除失败。
往集合中存入了两个元素,而将p1的age重新赋值后,就删除不了p1了,集合中仍有p1存在。时间长了内存泄露。
根据毕老师的讲解,集合中存入的不是对象本身,只是对象的引用。那照此看来,集合中仍有p1 的引用存在。
那么p1 的引用指向谁呢,是lili01,18还是lili01,16呢?是不是应该只有一个lili01 对象呢?
删除元素时是拿着引用找到堆内存中对象,再调用对象的hashCode算法算出哈希值,在和集合中的哈希值比较吗?

评分

参与人数 1技术分 +1 收起 理由
谭立文 + 1

查看全部评分

6 个回复

倒序浏览
我认为,删除失败的原因 是重写了 hashCode() 方法,而不是,将p1的age重新赋值后,就不能删除了
回复 使用道具 举报
HashSet hs = new HashSet();
                Person p1 = new Person("lili01", 18);
                System.out.println("p1==" + p1.hashCode() + "age=" + p1.age);// 打印源p1的值       
                                                                                                                                                // hs存进来的就是该数值
                Person p2 = new Person("lili02", 20);
                hs.add(p1);
                hs.add(p2);
                p1.age = 225;
                System.out.println("p1==" + p1.hashCode() + "age=" + p1.age);// 打印属性值改变后的的p1                                                                                                                                       
                System.out.println(hs.remove(p1));// 返回false,删除失败。因为hs是用p1现在hashcode值来在集合中遍历,自然无法删除(集合中的hashcode没改变)
                for (Iterator it = hs.iterator(); it.hasNext();) {
                        Person p = (Person) it.next();
                        System.out.println("px==" + p.hashCode() + "=====age=" + p.age);// 而集合中的对象实际上是还是p1,只是属性值和hashcode都改变过了
                }
                System.out.println(hs.size());
                System.out.println("重写的hashcode方法最好写的复杂点");
回复 使用道具 举报
当一个对象被存储进HashSet集合中以后,就不能修改这个对象中那些参与计算哈希值的字段了,否则,对象修改以后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果(字段值一改变,hashcode就会改变,相应的引用地址也会改变,导致以前的引用作废),这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
package staticImport.cn.itcast.day1;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
public class ReflectTest2 {
   public static void main(String args[])throws Exception{
/**已经知道集合类型*/
             Collection<ReflectPoint> collections = new HashSet<ReflectPoint>();
             ReflectPoint pt1 = new ReflectPoint(3,3);
             ReflectPoint pt2 = new ReflectPoint(5,3);
             ReflectPoint pt3 = new ReflectPoint(3,3);
             collections.add(pt1);
             collections.add(pt2);
             collections.add(pt3);
             collections.add(pt1);
             /*1、当重写了ReflectPoint中的hashCode与equals方法之后pt4就存不到collections中了
             这时候,size的值为2*/        
/*某一个对象存入到集合中之后,参与hashCode运算的属性就不要改动了,改完之后就会造成内存泄露。如果不断的在
              * 集合中添加对象,修改对象,删除对象,以此循环,最终的结果就是内存溢出*/
             pt1.y = 9;//修改属性y导致pt1的hashCode改变,以前的引用变得没有作用,导致remove不掉
             System.out.println(collections.remove(pt1));//false
             System.out.println(collections.size());
   }
}
class ReflectPoint{
private int x;
public int y;
public String str1 = "basketball";
protected String str2 = "aaaaaaaaaaCCCCCCCC";
public String str3 = "ababababa";
public ReflectPoint(int x, int y) {
  super();
  this.x = x;
  this.y = y;
}
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 String toString(){
  return str1+"  "+str2+"  "+str3;
}
//在ReflectTest2中要使用
@Override
public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + x;
  result = prime * result + y;
  return result;
}
@Override
public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  ReflectPoint other = (ReflectPoint) obj;
  if (x != other.x)
   return false;
  if (y != other.y)
   return false;
  return true;
}
public String getStr1() {
  return str1;
}
public void setStr1(String str1) {
  this.str1 = str1;
}

}

评分

参与人数 1技术分 +1 收起 理由
谭立文 + 1

查看全部评分

回复 使用道具 举报
那两个这个对象就没有引用了?
回复 使用道具 举报
那么p1 的引用指向谁呢,是lili01,18还是lili01,16呢?是不是应该只有一个lili01 对象呢?
  
    在这个例子中,p1的引用从开始到结束都没有改变,始终指向内存中的那个对象,p1在HashSet中的位置也没有改变,始终在最初HashCode()值所对应的存储地方。
       那什么改变了?
       HashSet寻找pt1的方法改变了,pt1的age属性改变后,导致HashSet寻找pt1所计算出来的HashCode与最初的HashCode不一样,也就是没有找到正确的存储空间,导致了pt1删除不掉。pt1依旧存在它最开始所在的区域,只是HashSet找错了。

删除元素时是拿着引用找到堆内存中对象,再调用对象的hashCode算法算出哈希值,在和集合中的哈希值比较吗?
       是这样的,删除和查找都需要哈希值,换句话说只要是哈希值一样,并且equals方法返回true,就算传入的引用不是指向pt1也能删除HashSet集合中的pt1。
   
回复 使用道具 举报
黑马张旭明 发表于 2012-10-30 12:29
那么p1 的引用指向谁呢,是lili01,18还是lili01,16呢?是不是应该只有一个lili01 对象呢?
      在这个例 ...

谢谢你的回答,很简明!
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马