黑马程序员技术交流社区

标题: HashSet代码关于增删元素及重写equals()及hashCode()方法问题 [打印本页]

作者: 魏-玉-彪    时间: 2012-7-27 19:05
标题: HashSet代码关于增删元素及重写equals()及hashCode()方法问题
/*
* 在以下HashSet测试代码中
*
* 1  为什么
*                 c.add(new Person("Jack", 20));
                c.add(new Person("Jack", 20));
               
                HashSet c 可以加入两个同样参数的Person对象,却不能
               
        移除c.remove(new Person("Jack", 20))?
               
        2         加入的重写equals()及hashCode()方法中,“i = 10 * i + age;”
       
       
        的意义是什么?
       
                public int hashCode() {
                        int i;
                        i = (name == null ? 0 : name.hashCode());
                        i = 10 * i + age;
                        return i;
                }
*
* 3  HashSet移除 一个对象时,remove方法中的参数中为什么还要NEW一个对象?
       
           c.remove(new Integer(25));
         System.out.println(c.remove(new Person("Jack", 20)));
*
*
*
*
*/




package com.itheima;
import java.util.*;
class Person {
        private String name;
        private int age;
        public Person(String n, int a) {
                this.name = n; this.age = a;
        }
       
        /*public boolean equals(Object obj) {
                if (obj instanceof Person) {
                        Person p = (Person) obj;
                        return (name.equals(p.name)) && (age == p.age);
                }
                return super.equals(obj);
        }
       
       
       
       
        public int hashCode() {
                int i;
                i = (name == null ? 0 : name.hashCode());
               
                i = 10 * i + age;
                return i;
        }
        */
}
public class HashSetTest {
        public static void main(String[] args) {
                Collection c = new HashSet();
                c.add(new Person("Jack", 20));
                c.add(new Person("Jack", 20));
       
                Person p2= new Person("Jack", 20);
                c.add(p2);
               
               
               
               
                System.out.println(c.size());
               
               
                System.out.println(c);
                c.add(new Integer(25));
                c.add("Hello World");
               
                System.out.println(c.size());
               
               
                c.remove(new Integer(25));
               
               
                c.remove("Hello World");
                System.out.println(c.size());
               
                System.out.println(c.remove(new Person("Jack", 20)));
               
                System.out.println(c.remove(p2));
               
                System.out.println(c.size());
        }
}



作者: 杨志    时间: 2012-7-27 20:50
鉴于你的问题。给你看些代码可能会好点!咱们就来看看JDK 的源码是怎么样实现hashSet的。
其实是这样的!先看下代码!
  1. /**
  2.      * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
  3.      * default initial capacity (16) and load factor (0.75).
  4.      */
  5.     public HashSet() {
  6. map = new HashMap<E,Object>();
  7.     }
复制代码
这是JDK中的HashSet的构造方法。
这里看到这里的HashSet其实是采用了HashMap去存储的。
那么这里hashSet的add 方法又是怎么样呢!如下:
  1. public boolean add(E e) {
  2. return map.put(e, PRESENT)==null;
  3.     }
复制代码
也是用了hashMap的put方法。这里的PRESENT只是一个Object对象。一个final变量。
那么好的!我们就去看看HashMap中的put方法是怎么样做的!
  1.     public V put(K key, V value) {
  2.         if (key == null)
  3.             return putForNullKey(value);
  4.         int hash = hash(key.hashCode());
  5.         int i = indexFor(hash, table.length);
  6.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  7.             Object k;
  8.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  9.                 V oldValue = e.value;
  10.                 e.value = value;
  11.                 e.recordAccess(this);
  12.                 return oldValue;
  13.             }
  14.         }
  15.         modCount++;
  16.         addEntry(hash, key, value, i);
  17.         return null;
  18.     }
复制代码
可以到这里
  1. int hash = hash(key.hashCode());
复制代码
这句表明其实,将传入的Key先转换成hashCOde.然后再进行移位运算。
而这里你并没有对该对象的hashCode方法进行重载。那么就是说两个hashCode值不同。
  1. public class Test{

  2. public static void main(String[] args){

  3. Person p1 = new Person("tt");
  4. Person p2 = new Person("tt");

  5. System.out.println(p1.hashCode());
  6. System.out.println(p2.hashCode());

  7. }
  8. }
  9. class Person{
  10. private String name;
  11. public Person(String name){
  12. this.name = name;
  13. }
  14. }
复制代码
所以可以看出其实存入了两个对象的。
我想现在你应该知道hashCode方法存在的意义了。
当你去重写hashCode方法,根据值去生成hashCode.那么在底层发现两次的值是一样的。
这样就不会去保存。也就额可以避免重复。我想现在  equals()方法你也知道是为什么了吧!
也是为了避免重复添加。
现在在去看看 remove方法是怎么完成的。
  1. public boolean remove(Object o) {
  2. return map.remove(o)==PRESENT;
  3. }
复制代码
一样也是调用了map的方法。而map的方法是怎么样的呢?
  1. public V remove(Object key) {
  2. Entry<K,V> e = removeEntryForKey(key);
  3. return (e == null ? null : e.value);
  4. }
复制代码
一样的也是根据key值去删除的。在看看你的代码:
  1. c.remove(new Person("Jack", 20))
复制代码
你是新建了一个对象。那么请问这个对象以前有么?肯定是没有的。注意判断的根据是根据hashCode值 哦!
所有remove一个hashSet中根本就没有的值肯定是不可以的。
我不知道为什么你会说hashSet中的remove方法要new一个对象。看下面代码:
  1. import java.util.*;
  2. public class Test{

  3. public static void main(String[] args){
  4. Set set=new HashSet();

  5. Person p1 = new Person("tt");
  6. Person p2 = new Person("tt");
  7. set.add(p1);
  8. set.add(p2);
  9. System.out.println(set.size());
  10. set.remove(p2);
  11. System.out.println(set.size());
  12. System.out.println(p1.hashCode());
  13. System.out.println(p2.hashCode());
  14. }
  15. }
  16. class Person{
  17. private String name;
  18. public Person(String name){
  19. this.name = name;
  20. }
  21. }
复制代码
这样就可以删除了撒!因为p2存在的,而不是new 的一个对象。
好了说了这么多!建议碰到不会的去看看JDK 。真的很有帮助。希望你能有点帮助。
个人还是推荐多看JDK源码。那才真的是一座宝库!




作者: 张雪磊    时间: 2012-7-27 21:26
第一个问题:
由于一开始楼主没有覆盖equals方法,所以只要楼主new一次Person("Jack", 20)),就在堆内存中创建了一个对象,也就是说写了下面这三个语句
                 c.add(new Person("Jack", 20));//1
                 c.add(new Person("Jack", 20));//2
                 c.remove(new Person("Jack", 20));
楼主就建立了三个对象,既然是三个对象,那这三个对象当然不是同一个东西,而楼主删掉的是上面红色的那个新建的对象,与上面两个都无关,看起来自然就像是没删掉了。

第二个问题:
通过上面也能看出来一点equals的作用,equals里面定义了如何判断两个Set集合元素是否相等。
具体的说由于Set集合里的元素是不重复的,那当加入一个新元素就需要那这个元素与集合里的其他元素作比较,如果集合中已存在要加入的元素,也就是符合自己定义的equals里的判断方式,那就不会添加这个元素,以保证集合里的成员唯一。
hashCode的作用也是保证Set集合里成员唯一,而且是先判断hashCode的值是否相等,如果相等在判断equals是否相等,如果都相等,那说明集合中已经存在相同成员了。至于为什么 要写成  i  = 10 * i + age; 先要说说这句代码要做什么,就拿上面的1,2两个对象来说,i 就是那两个对象中jack对应的hash值,都是jack所以这两个是相同的,在加上age,都是20当然也相等了。但有一种特殊情况,假如有这样两个对象
              new Person("Jack", 20));
             new Person("Jahn",50));
这两个肯定不相等,但恰好jack对应的hash值是50,jahn的hash值是20。20+50=50+20,这样本来不应该相等的两个元素,却被判断为相等。所以年龄乘以随便一个数就可以避免这种情况发生。

第三个问题:
集合里需要存储的是对象,对于一个类来说,就那一个,干嘛要用集合存他,一个类可以创建很多对象,这些对象可以有不同的内容,这样才形成一个集合,然后用集合的方法错做这些成员。才有意义!

作者: 吴立杰    时间: 2012-7-27 23:18
HashSet c 可以加入两个同样参数的Person对象,却不能
移除c.remove(new Person("Jack", 20))?
答:你加入的是两个不同的对象,你移除的是个根本不在集合内的一个新对象,建议楼主以后不要用匿名对象,这样以后不好操作这些数据了。
加入的重写equals()及hashCode()方法中,“i = 10 * i + age;”的意义是什么?
答:这里是想按照自己的方式去除你认为重复的自定义对象,那句话的意思是在根据名字和年龄算hash值,因为hashset存放数据时根据hash值分区存放的,hash值相同了会走equals方法,继续判断重复内容。看来楼主这个地方的问题很大,建议重看一遍毕老师的视频,耐心理解,加油!
作者: 杨志    时间: 2012-7-31 16:52
杨志 发表于 2012-7-27 20:50
鉴于你的问题。给你看些代码可能会好点!咱们就来看看JDK 的源码是怎么样实现hashSet的。
其实是这样的!先 ...

嘿嘿!对你有帮助就好!加油!
作者: 魏-玉-彪    时间: 2012-7-31 16:54
谢谢各位大侠,本问题已解决
作者: hello world    时间: 2012-7-31 21:59
汗,先建议一下你个人去看看HashSet的源代码,你会发现HashSet底层用的是HashMap,你在看看HaspMap的源代码,至于hasocode  你不要过于追究为什么这样子写,他就是一种算法,可能那样子写重复的可能性小。




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2