List允许重复且保留顺序,适合日志、队列;Set禁止重复、不保证顺序(除非LinkedHashSet/TreeSet),适合去重、权限校验;自定义对象入Set须重写hashCode()和equals();List支持索引访问,Set不支持;底层实现决定性能:ArrayList(数组)、LinkedList(链表)、HashSet(HashMap)、TreeSet(红黑树);均非线程安全,需显式同步。

元素是否允许重复?这是最根本的分水岭
如果你往 List 里加两次 "apple",它会老老实实存两个;但往 Set 里加两次,第二次会静默失败——add() 返回 false,集合长度不变。这不是 bug,是设计契约:List 是「序列」,Set 是「数学意义上的集合」。
-
List允许重复 + 保留插入顺序 → 适合记录操作日志、消息队列、带索引的配置项 -
Set禁止重复 + 不保证顺序(除非用LinkedHashSet或TreeSet)→ 适合去重、权限校验、唯一ID缓存 - 自定义对象进
Set必须重写hashCode()和equals(),否则哪怕内容相同也会被当成不同元素
能不能按索引取值?决定了你能否“随机访问”
List 支持 get(2)、set(0, "new")、indexOf("x") 这类基于下标的操作;Set 没有下标概念,连 get() 方法都没有。想遍历只能用 iterator() 或增强 for 循环。
- 需要根据位置快速查/改数据?选
List(优先ArrayList) - 只关心“有没有这个值”,不关心它在第几位?
Set更语义清晰,且contains()平均是 O(1)(HashSet)或 O(log n)(TreeSet),比List.contains()的 O(n) 快得多 - 误写
mySet.get(0)会直接编译报错——接口压根没定义这个方法
底层实现差异直接决定性能拐点
ArrayList 是数组,LinkedList 是双向链表,HashSet 底层其实是 HashMap(只用 key),TreeSet 是红黑树。这些不是 trivia,而是你选错就卡顿的根源。
- 高频尾部增删 + 偶尔遍历?
ArrayList和LinkedList差距不大;但频繁中间插入?LinkedList的 O(1) 优势才显现(不过实际中 JVM 优化和 cache 局部性常让ArrayList更快) - 大量
add()+ 后续反复contains()?别用ArrayList做去重,HashSet是天然解法 - 需要有序且去重?别先用
ArrayList再Collections.sort()+ 手动去重,直接上TreeSet或LinkedHashSet(按插入序)
线程安全不是默认选项,别指望接口帮你兜底
List 和 Set 接口本身都不承诺线程安全。所有常用实现(ArrayList、HashSet、LinkedHashSet、TreeSet)都是非线程安全的。曾经的 Vector 和 Hashtable 已淘汰,现在要用得靠 Collections.synchronizedList() 或 CopyOnWriteArrayList 等显式包装。
立即学习“Java免费学习笔记(深入)”;
- 多线程写同一个
ArrayList?可能抛ConcurrentModificationException,也可能数据 silently 错乱 -
HashSet在并发add()时可能因哈希冲突导致扩容死循环(JDK 7 及以前),JDK 8+ 改为红黑树缓解但仍未解决线程安全 - 真要并发安全,优先考虑
ConcurrentHashMap的 keySet 视图,或CopyOnWriteArraySet(仅适用于读多写少场景)
ArrayList 存用户 ID 做去重判断,既慢又易错;又比如往 HashSet 里扔没重写 hashCode() 的 DTO,结果发现重复提交永远过不去。这些都不是 API 不好用,是你没对齐它的设计意图。










