Set的核心特点是无序、唯一、无索引;它不保证元素顺序,不允许重复,不支持索引访问,依赖hashCode和equals(或compareTo)实现去重,不同实现类在性能、排序与顺序保持上各有侧重。

Set的核心特点:无序、唯一、无索引
Java中Set接口最根本的定位,就是表示一个不包含重复元素的集合。它不关心元素顺序,也不提供按位置访问的能力——没有get(int index)这类方法。这意味着你不能像List那样用下标取值,只能通过迭代器或增强for循环遍历。
它允许最多一个null元素;所有实现类(如HashSet、TreeSet、LinkedHashSet)都遵守“唯一性”这一契约,但实现方式不同:HashSet靠哈希+equals,TreeSet靠比较逻辑,LinkedHashSet则在保证唯一的同时还记住了插入顺序。
去重原理依赖hashCode和equals方法
以最常用的HashSet为例,它的底层其实是用HashMap实现的:把要存的元素作为key,value固定为一个空对象。而HashMap的key天然不允许重复,这就把去重任务交给了哈希表机制。
具体判断是否重复分两步:
立即学习“Java免费学习笔记(深入)”;
- 先调用元素的hashCode()方法,计算出哈希值,决定该元素应落在哈希表哪个桶(bucket)里
- 如果桶里已有元素,再调用equals()方法逐个比对,只有两者都返回true,才认定是同一个元素,拒绝添加
所以如果你往HashSet里放自定义对象(比如Student),却没重写hashCode和equals,哪怕两个对象内容完全一样,也会被当成不同元素加入——因为默认的hashCode是内存地址,equals是比较引用。
不同Set实现类的去重与排序差异
虽然都实现Set接口,但三类常用实现处理“顺序”和“去重依据”的方式不同:
- HashSet:最快,平均O(1)增删查;无序;依赖hashCode/equals
- TreeSet:自动按自然顺序或自定义Comparator排序;去重靠compareTo()或compare()返回0来判定相等;时间复杂度O(log n)
- LinkedHashSet:保留插入顺序;底层用链表维护顺序,哈希表保证唯一;性能略低于HashSet,但顺序可预测
选哪个,取决于你更看重速度、排序,还是插入顺序的可重现性。
常见误区与注意事项
Set的“无序”不是指随机,而是不承诺任何特定顺序——尤其是HashSet,每次运行输出可能不同,这不是bug,是设计使然。
另外要注意:
- 修改已存入HashSet中的对象的属性(且这些属性参与了hashCode/equals计算),可能导致该对象再也无法被contains()或remove()正确识别
- Set本身线程不安全;多线程环境下需用Collections.synchronizedSet()包装,或改用ConcurrentSkipListSet等并发安全替代方案
- addAll()操作会自动跳过当前已存在的元素,不会报错也不会覆盖,这是Set“去重语义”的自然延伸










