set集合属于collection集合的一个子类。和List集合不同的是:
set集合元素具有唯一性
set集合元素无序
set集合元素只允许一个null值
不是线程安全的(效率高)
唯一性
学过list集合的可能都会知道,list集合中添加什么元素就会有什么元素,即使添加多个相同的元素,list集合也都会保存下来。但是对于set集合来说就不是这样了。set集合的很重要的一个特点就是元素的唯一性,即相同的元素只会保存下来一个。
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("小明");
set.add("小红");
set.add("小红");
set.add("小青");
set.add("小明");
set.add("老王");
set.add("小蓝");
set.add("小蓝");
System.out.println(set);
}
相应的结果:
[小明, 老王, 小蓝, 小红, 小青]
可以很清楚的发现,set集合将重复添加的元素过滤掉了。
但是对于对象类型,好像并不是这样,首先先看一下实例。
public static void main(String[] args) {
Set<User> set = new HashSet<>();
set.add(new User("小王",20));
set.add(new User("小王",20));
set.add(new User("小蓝",20));
set.add(new User("小里",20));
System.out.println(set);
}
对应的结果
[User{name='小王', age=20}, User{name='小里', age=20}, User{name='小王', age=20}, User{name='小蓝', age=20}]
我们发现,set集合并没有达到我们预期的效果。
为了解决这个问题,我们需要去阅读一下jdk的源代码。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
通过代码的跟踪,发现set集合的add方法最终调用的是putVal方法,该方法有一个参数hash(key),这个方法返回的就是key值对应的一个hash值。
贴下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;
}
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
这两句主要就是在set集合中根据当前的hash值查找是否已经存在了一个对象,如果不存在,就会新创建一个对象并保存。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
如果已经存在了,就会走else分支,执行上述代码。判断hash值和两个对象的地址是否相等,如果都相等,则代表重复了,就执行去重操作。
所以想要解决对象不会去重的问题,关键就是要解决hash值和地址的问题。那我们又知道,只有地址相同的对象才是相同的对象,所以如果要实现去重的效果,就要重写对象的equals方法和hashCode方法!
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public boolean equals(Object obj) {
boolean bool = false;
if (obj instanceof User){
User user = (User) obj;
if (user.name != null && this.name.equals(user.name) && this.age == user.age){
bool = true;
}
}
return bool;
}
修改equals的判断原则。
运行结果
[User{name='小蓝', age=20}, User{name='小里', age=20}, User{name='小王', age=20}]
无序性
HashSet集合操作,是将数据值存入至HashMap的key中;
hashmap在保存数据时,顺序是通过计算key的hash值和当前数组长度的 & 运算,计算保存数据的下标位置。所以说set是无序的。
持续更新~~