遇到一个蛋疼的问题,明知道是错的,但是不知道该怎么
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方法。
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方法。
hashcode: 1 2 3
1 2 3 4 5 1 2 3 4 6 7 8 9 0 10
首先根据hashcode的值来确定对象位于哪一个区域,是在一区域还是2区域还是3区域。当确定了区域之后,在用equals方法,来确定该区域是否有该对象存在。也就是说,不同的区域是可以存放同意对象的。
既然是两个对象,为啥不管是用==还是用equals方法,得到的结果都是true?
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中不能存放重复的元素这一说法是没有问题的;
// set在存储数据时需要遍历之前所存放的数据(hashCode与内存地址),用于与当前所要添加的元素进行对比。若得出结果相等,
// 则不添加。比较的方法是:首先比较两个对象的hashCode,如果不相等,则直接添加;否则,再用equals()(比较地址)
// 方法进行比较,如果结果返回为true,则不添加;否则,添加 这是必须遵守的规则,但是规则内部的细节是由你自己来定义的(如代码中直接给hashCode赋值等具体细节的操作),最终的输出结果根据具体实现的细节决定。
//首先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次
所以说,在集合中,比较两个对象是否相等的时候,只要hashcode不相等,对象肯定不相等。
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次
提供哈希表的性能。
对于存于set中的对象类最好重写equals()和hashCode()来遵守这个规则。
这样就很明显了,你先的hashcode是2,那么计算出来的hash值肯定跟后来改成3的时候计算出来的是不一样的,所以也就都存进去了。
但是ao跟ao2其实就是引用的同一个对象,所以不管是equals和==他们都是相等的。
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());