HashSet集合不能存储重复的元素,那么元素之间是否重复,HashSet是根据什么机制去判断的呢?
HashSet在添加一个元素时(比如此时添加的是”a”这个元素),都会将该元素与set中所遍历到的每个元素作比较,比较的过程是这样的:先用该元素的hashCode值与遍历到的每个元素的hashCode作比较,如果hashCode不相等,则直接添加;若hashCode的值一样,则继续用该元素的equals()方法比较(是被添加的equals()方法,与之比较的元素作为参数),如果equals()方法得到的值是一样的,不再添加,如果equals()的值是不一样的,就会将该对象添加到其他内存地址(重新计算出不一样的hashCode)。
下面举个例子:
package cn.test.set;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
public class TestHashSet {
public static void main(String[] args) {
Set set = new HashSet();
set.add(new A());
set.add(new A());
set.add(new B());
set.add(new B());
C c1 = new C();
C c2 = new C();
set.add(c1);
set.add(c2);
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(set);
}
}
class A{
public boolean equals(Object obj){
return true;
}
}
class B{
public int hashCode(){
return 1;
}
}
class C{
public static int count = 0;
public int a;
public C(){
count++;
a = count;
}
public int hashCode(){
return a;
}
public boolean equals(Object obj){
return true;
}
}
得到的结果是:
1
2
[cn.test.set.B@1, cn.test.set.B@1, cn.test.set.C@2, cn.test.set.A@152b6651, cn.test.set.A@6bbc4459]
添加到set里的元素有两个B元素,两个A元素和一个C元素。
为什么有两个A元素?
因为A类没有重写hashCode()方法,在new 两个同一个类的对象时,hashCode肯定是不同的,所以两个A都会被添加进去。
为什么会有两个B元素?
B类重写了hashCode(),返回1,首先在set里面有两个A元素的情况下,我们知道没有重写hashCode(),A返回的hashCode()绝对不可能出现返回1的值。当添加第一个B的时候,能添加成功,当添加第二个B的时候,比较第一个B的hashCode,发现相同;相同就继续用被添加的元素的equals去比较,由于没有重写,所以两个new出来的对象用equals()比较也基本不可能相同。
为什么只要一个C,而且C是@2而不是@1?(也就是为什么是hashCode返回2的那个C被添加进去了?)
当添加第一个C的时候,hashSet会将C逐个与其他元素比较(先hashCode()后equals()),当跟第一个B比较的时候,发现他们的hashCode是一样的,那么就继续用第一个C的equals()的方法,第一个B作为参数,发现返回true,hashSet就认为他们是相等的,所以抛弃了,但添加第二个C的时候就不会了。
将类B的hashCode从返回1改为返回100,得到的结果如下:
1
2
[cn.test.set.C@1, cn.test.set.C@2, cn.test.set.B@64, cn.test.set.B@64, cn.test.set.A@152b6651, cn.test.set.A@6bbc4459]
可以用刚刚的思路去理解一遍,发现解得通
=======
所以,建议我们把要添加到hashSet的对象的类最好重写hashCode()方法和equals()的方法,并且equals()相等时,hashCode()也相等。这样的话,只要我们用equals()判断返回true,就一定是相等的对象,如果判断返回false,就一定是不相等的对象
这里有两条原则:
1、当两个对象的equals()相等时,hashCode()返回的值是一样的
2、对象中用作比较equals()的属性,都用来计算hashCode()
=====================================================
向HashSet添加可变对象时,必须十分谨慎,不能修改已经添加到HashSet里面的元素,否则会造成HashSet的混乱,看以下代码:
package cn.test.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Stack;
import java.util.Vector;
public class TestHashSet2 {
/**
* 当向hashSet添加可变类的时候必须十分小心,添加到hashSet
* 后最好不要再修改里面的值,不然会造成混乱
* @param args
*/
public static void main(String[] args) {
HashSet<R> set = new HashSet<R>();
set.add(new R(5));
set.add(new R(-3));
set.add(new R(9));
set.add(new R(-2));
System.out.println(set);
Iterator<R> it = set.iterator();
R r = it.next();
r.count = -3;
System.out.println(set);
set.remove(new R(-3));
System.out.println(set);
System.out.println("set是否包含-3:"+set.contains(new R(-3)));
System.out.println("set是否包含5:"+set.contains(new R(5)));
}
}
class R{
int count;
public R(int count){
this.count = count;
}
@Override
public int hashCode() {
return this.count;
}
@Override
public boolean equals(Object obj) {
//只有对象的count相等,就认为他们是相等的
if(obj instanceof R){
R r = (R)obj;
if(r.count == this.count){
return true;
}
}
return false;
}
@Override
public String toString() {
return "R [count=" + count + "]";
}
}
输出结果:
[R [count=5], R [count=9], R [count=-3], R [count=-2]]
[R [count=-3], R [count=9], R [count=-3], R [count=-2]]
[R [count=-3], R [count=9], R [count=-2]]
set是否包含-3:false
set是否包含5:false
结果分析:
在第一次输出整个set的时候,没什么问题;然后程序修改了set用迭代器迭代出来的第一个元素(根据结果显示,这个元素是count=5)的count为-3,然后删除了count=-3的元素set.remove(new R(-3));
,删除的过程是这样的:根据要删元素计算出他的hashCode值,然后在set找到这个索引的位置,用remove的参数的equals判断是否相同,是的话就删除元素,否则不删;所以这里把真正的count=-3给删除了(不是修改后的),然后程序System.out.println("set是否包含-3:"+set.contains(new R(-3)));
,同样的,程序会根据set.contains(new R(-3))
里的new R(-3)计算hashCode,发现set中没有这个元素,于是报false,接着程序System.out.println("set是否包含5:"+set.contains(new R(5)));
,同理计算new R(5)的hashCode,发现set中的有这个位置的元素,然后用contains()参数的equals判断相不相同,发现不相同,所以报false