遇到一个蛋疼的问题,明知道是错的,但是不知道该怎么
public class AddObject {   
    int hashCode;   
  
    public AddObject(int hashCode) {   
  
        this.hashCode = hashCode;   
    } public int hashCode() {

return hashCode;
}   
}  public class TestHashSet {   
    public static void main(String args[]) {   
        // set在存储数据时需要遍历之前所存放的数据(hashCode与内存地址),用于与当前所要添加的元素进行对比。若得出结果相等,   
        // 则不添加。比较的方法是:首先比较两个对象的hashCode,如果不相等,则直接添加;否则,再用equals()(比较地址)   
        // 方法进行比较,如果结果返回为true,则不添加;否则,添加   
        Set<AddObject> set = new HashSet<AddObject>();   
        // 设置初始hashCode值   
        AddObject ao = new AddObject(2);   
        // 第一次添加   
        set.add(ao);
        AddObject ao2= ao;
       
        // 重新设置hashCode   
        ao2.hashCode=3;
        
       
        System.out.println(ao.equals(ao2));
        // 再次添加   
        set.add(ao2);
        Iterator iter = set.iterator();
        while(iter.hasNext()){
         AddObject aoTest = (AddObject)iter.next();
         System.out.println(aoTest.hashCode+"xxx  ");
        }
        System.out.println(ao.equals(ao2));
        System.out.println(set.size());   
        // 输出结果显然是2。但两次添加的对象显然是同一个对象,所以Set中不能存放重复的元素这一说法是不完善的   
  
    }   
}  
反驳,代码贴在这里,望大神看一下,特别是写的这个hashCode方法。

解决方案 »

  1.   

    既然你自己都说了是根据hashcode来进行存放的。那么别人就是根据这一根据来进行存放的。你hashcode变了,别人当然认为你是存放了两个对象了。打个比方:你是根据外观来判断一个事物,比如芙蓉姐姐,开始你觉得是芙蓉姐姐。但是当别人整容,减肥了之后。你认不出来了。你就以为是另外一个人了。
      

  2.   

    而且。这个hashcode其实是一个用来判定对象存储的区域的。例如:
    hashcode:   1         2            3    
            1 2 3 4 5  1 2 3 4 6   7 8 9 0 10
    首先根据hashcode的值来确定对象位于哪一个区域,是在一区域还是2区域还是3区域。当确定了区域之后,在用equals方法,来确定该区域是否有该对象存在。也就是说,不同的区域是可以存放同意对象的。
     
      

  3.   


    既然是两个对象,为啥不管是用==还是用equals方法,得到的结果都是true?
      

  4.   

    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
    /*
     class AddObject {   
        int hashCode;   
      
        public AddObject(int hashCode) {   
      
            this.hashCode = hashCode;   
        }    public int hashCode() {
            
            return hashCode;
        }   
    }  
    */
    public class TestHashset {   
        public static void main(String args[]) {   
            
            //Set<AddObject> set = new HashSet<AddObject>();   
            Set<String> set = new HashSet<String>(); 
            /*AddObject ao = new AddObject(2);   
           
            set.add(ao);
            
            AddObject ao2= ao;
           
            ao2.hashCode=3;
            
           
            System.out.println(ao.equals(ao2));
          
            set.add(ao2);*/
            
            String    s="qqq";
            set.add(s);
            String   ss=s;
            ss="www";
            set.add(ss);
            /*
            System.out.println(s.hashCode());
            System.out.println(s.hashCode());*/
         /*   Iterator iter = set.iterator();
            while(iter.hasNext()){
                AddObject aoTest = (AddObject)iter.next();
                System.out.println(aoTest.hashCode+"xxx  ");
            }
            System.out.println(ao.equals(ao2));*/
            System.out.println(set.size());   
            // 输出结果显然是2。但两次添加的对象显然是同一个对象,所以Set中不能存放重复的元素这一说法是不完善的   
      
        }   
    }  
     我将set 换成装入 String  类型变量   
    结果也是和楼主一样     并且确实在这个过程中 
    在 内存数据区 就只有一个地址被分配了
    (因为我测试了两个引用的hashcode是一样的)
    证明没有重新分配地址;后来查了一下api  找到了结果 set  重写了  equals(Object o)方法  
    set 在存放元素时,是检查自己的元素是否与现在要存放的元素相同;关键就在与这个方法object   的equals()方法比较的是两个对象是否有同一个引用,用以判断两个对象是否相等;set  重写的equals()方法,  比较的却是 两个对象的内容是否相等,   楼主 程序中   两个对象 显然  虽是同一个引用,但是对象内容已然不同,所以被判定是两个对象。
    所以Set中不能存放重复的元素这一说法是没有问题的;
      

  5.   


    // set在存储数据时需要遍历之前所存放的数据(hashCode与内存地址),用于与当前所要添加的元素进行对比。若得出结果相等,   
            // 则不添加。比较的方法是:首先比较两个对象的hashCode,如果不相等,则直接添加;否则,再用equals()(比较地址)   
            // 方法进行比较,如果结果返回为true,则不添加;否则,添加 这是必须遵守的规则,但是规则内部的细节是由你自己来定义的(如代码中直接给hashCode赋值等具体细节的操作),最终的输出结果根据具体实现的细节决定。
      

  6.   

    这个问题要查看源码
    //首先HashSet的代码    private transient HashMap<E,Object> map;    // Dummy value to associate with an Object in the backing Map
        private static final Object PRESENT = new Object();    ...    public Iterator<E> iterator() {
    return map.keySet().iterator();
        }    ...     public boolean add(E e) {
    return map.put(e, PRESENT)==null;
        }//可见HashSet是用HashMap来保存数据的//再看HashMap的代码    static int hash(int h) {
            // This function ensures that hashCodes that differ only by
            // constant multiples at each bit position have a bounded
            // number of collisions (approximately 8 at default load factor).
            h ^= (h >>> 20) ^ (h >>> 12);
            return h ^ (h >>> 7) ^ (h >>> 4);
        }    ...    public V put(K key, V value) {
            if (key == null)
                return putForNullKey(value);
            int hash = hash(key.hashCode());
            int i = indexFor(hash, table.length);
            for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                Object k;
                if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }        modCount++;
            addEntry(hash, key, value, i);
            return null;
        }    ...     static class Entry<K,V> implements Map.Entry<K,V> {
            final K key;
            V value;
            Entry<K,V> next;
            final int hash;        /**
             * Creates new entry.
             */
            Entry(int h, K k, V v, Entry<K,V> n) {
                value = v;
                next = n;
                key = k;
                hash = h;
            }
            ...
        }    ...    void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
            table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
            if (size++ >= threshold)
                resize(2 * table.length);
        }//可见一开始保存的ao,因为hashCode为2,被HashMap的hash方法计算得到一个hash值保存在table元素中
    //然后改变hashCode后重新保存的时候,因为此时计算的hash不同,所以被保存到新的一个table元素中
    //所以导致了set保存了2个元素,而ao和ao2引用的是同一个对象,所以就相当于同一个对象被保存了2次
      

  7.   


    所以说,在集合中,比较两个对象是否相等的时候,只要hashcode不相等,对象肯定不相等。
      

  8.   

    但是得到的两个对象的hashCode相等啊!
      

  9.   

    那如果是两个对象,为啥比较equals和==两个都相等。但是如果相等的话,set应该能存入啊!是不是在set外面是同一个对象,在set里面是不同的对象?
      

  10.   

    那么,既然是两个对象为啥用equals和==都得到true! 我知道,我那个程序是错误的。在调试的时候,set的table的hash一个是2,一个是3.但是就是不知道在外面怎么判断两者不是同一个对象
      

  11.   

    你还是没看明白我在8L的回复。
    HashSet内部用HashMap来保存数据。关键理解好HashMap的public V put(K key, V value)方法的代码。
    在LZ的例子中,set保存的ao和ao2是同一个对象,为什么同一个对象被保存了2次,因为从源代码上看可以知道,当第1次保存的时候,hashCode是2,内部的HashMap通过hash方法计算得到一个哈希值,然后查找该哈希值不存在,就把ao保存到table数组中,第2次保存的时候,因为改变了hashCode为3,所以hash方法计算得到的哈希值在table数组中没找到,也就是造成if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 这里不满足if条件,于是把ao2看作一个新的对象,重新保存到table数组中,也就是执行addEntry(hash, key, value, i);这样就造成了同一个对象被保存了2次
    LZ自己再好好理解一下源代码的实现吧,最终的原因就是内部的HashMap保存数据的时候会自己通过hash方法计算一个哈希值,然后判断该哈希值是否存在,存在则覆盖,不存在则追加,所以前后2次hashCode不一样导致hash计算的哈希值不一样,于是执行了2次addEntry,即保存了2次,实际上2次添加的是相同对象,只是两次计算的hash不同,所以在table数组中保存了2次
      

  12.   

    个人觉得这个问题没意义。不是说有个规则嘛,对象等,hashcode等,对象不等,hashcode最好不等,说是为了
    提供哈希表的性能。
    对于存于set中的对象类最好重写equals()和hashCode()来遵守这个规则。
      

  13.   

    先在add的时候会通过hash计算得到一个内存地址,如果内存地址上已经有对象存在那么则会去比较这两个东西((k = e.key) == key || key.equals(k))。如果不存在那么则直接存入当前计算出来的内存位置。
    这样就很明显了,你先的hashcode是2,那么计算出来的hash值肯定跟后来改成3的时候计算出来的是不一样的,所以也就都存进去了。
    但是ao跟ao2其实就是引用的同一个对象,所以不管是equals和==他们都是相等的。
      

  14.   

    我将LZ的代码拷贝到本地编译不过,ao2.hashCode的hashCode属性在代码中没有提供,你用的是private修饰的hashCode.下面的代码打印出来的结果就是1
    Set<AddObject> sets = new HashSet<AddObject>();
    AddObject object = new AddObject(1);
    AddObject copyObject = object;
    sets.add(object);
    sets.add(copyObject);
    System.out.println("size "+sets.size());