简介
Set集合,继承自Collection集合,其特点为:插入无序,存储的元素不能重复,且不可根据index来访问,可以存储null。
Set集合的常用实现类有:HashSet
、TreeSet
HashSet
HashSet,内部无序,封装了HashMap,其实也就是用HashMap的key来存储值。
private transient HashMap<E,Object> map;
HashSet是如何进行去重操作的呢?我们来看一看其的add()方法或许能够找到答案
// 可以看到,HashSet中的add(),其实是调用了HashMap中的put()
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// HashMap中的put()
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// HashMap中的putVal()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
putVal()具体是以什么逻辑实现的我在这里不再赘述,有兴趣的小伙伴可以看看我之前写的关于HashMap的文章,这里我们只要关注,putVal()在什么时候给我们返回值了。通过观察,我们发现,当key已经存在时,putVal()会给我们返回该key对应的旧值。还有一种情况就是,如果key不存在,则会为我们返回null。但是,由于HashSet在add的时候,已经内部将我们的value写死了:
return map.put(e, PRESENT)==null;
所以,如果一个key存在,必然返回的是PRESENT。
而HashSet中的add()方法就是通过这种方式为防止我们添加重复元素的。当这个key不存在集合中时,底层putVal返回null,然后返回上层调用的map.put(e,PRESENT)== null ,判断为true,于是添加成功,反之添加失败。
注意!!当我们像HashSet中添加自定义类型时,必须要让自定义类型重写hashCode()和equals()方法。
class People{
private int age;
private String name;
public People(){
}
public People(int age,String name){
this.age = age;
this.name = name;
}
}
public class SetTest {
public static void main(String[] args) {
People p1 = new People(10,"张三");
People p2 = new People(10,"张三");
Set<People> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 在没有重写equals() 和 hashCode()的前提下,输出 2。这是为什么呢?
}
}
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
这是HashMap判断key是否存在时调用的,我们看到,最终我们都会调用key.equals(),即调用key所在类的equals()方法,如果我们没有重写equals(),则会调用Object类中的equals(),而我们知道,Object类中的equals()方法内部实现是由==来实现的,而==
在比较引用数据类型时,比较的是地址值,这样,即使我们new 出来两个相同的People对象,即p1、p2.但是两个对象的存储地址是不同的,那么key != null && key.equals(k)
就会返回false,从而导致整个if语句中得条件判断返回false。于是就会将相同的key放入set集合中。