List允许重复、保持插入顺序、支持索引访问;Set不允许重复、顺序取决于实现类(HashSet无序、LinkedHashSet保插入序、TreeSet按排序)、不支持索引访问。

元素是否允许重复
这是最直观的区别:List 允许重复元素,Set 不允许。比如往 ArrayList 中连续添加两次 "a",大小是 2;而往 HashSet 中加两次,大小仍是 1。
背后原因是:
-
List关注“顺序”和“位置”,靠索引访问,不强制去重 -
Set的设计目标就是“唯一性”,添加时会调用equals()和hashCode()判断是否已存在
注意:如果自定义类没重写 equals() 和 hashCode(),即使两个对象逻辑相同,Set 也可能存入多个——这是最常见的误用点。
是否保证插入顺序
List(如 ArrayList、LinkedList)天然保持插入顺序;Set 的行为则取决于实现类:
-
HashSet:不保证顺序(底层是哈希表,遍历顺序不确定) -
LinkedHashSet:保证插入顺序(链表维护顺序) -
TreeSet:按自然序或比较器排序,不是插入顺序
所以别默认 Set 有顺序——要用插入顺序,必须显式选 LinkedHashSet,而不是靠猜测。
立即学习“Java免费学习笔记(深入)”;
能否通过索引访问元素
List 支持 get(int index),可以随机访问;Set 没有索引概念,不能直接按位置取值。
常见误区操作:
- 想取
Set的“第一个元素”?得先转成Iterator或数组:set.iterator().next()或set.toArray()[0] - 想遍历并带下标?只能手动计数,不能用
for (int i = 0; i
性能上,List.get() 是 O(1)(ArrayList),而 Set 的任何“按位访问”都要先构造中间结构,开销更大。
底层数据结构与典型使用场景
List 常用于需要顺序处理、允许重复、可能频繁按索引读写的场景,比如日志列表、参数队列;Set 更适合去重、成员检查、关系建模(如用户权限集合)。
几个关键差异点:
- 查是否存在:
Set.contains()平均 O(1)(HashSet),List.contains()是 O(n) -
内存占用:
Set因需维护唯一性,通常比等量List占更多内存 - 线程安全:
Collections.synchronizedList()和Collections.synchronizedSet()行为一致,但CopyOnWriteArrayList无对应Set实现
真正容易被忽略的是:很多开发者用 List 接收数据库查询结果后,再手动去重——其实该在业务层就用 Set 接收,既语义清晰,又避免后续 stream().distinct() 这类补救操作。










