Java集合中List与Set的区别及体系全览
1. 引言
在 Java 开发中,集合(Collection)是最常用的工具之一。无论是存储一组对象、遍历数据,还是进行去重、排序操作,都离不开 List、Set 等集合接口。然而,很多初学者对集合的整体架构一知半解,对于 “List 是有序、可重复的;Set 是无序、不可重复的” 只停留在死记硬背,并不理解其底层原理和适用场景。
本文将带你从顶层 Collection 接口出发,全面梳理 Java 集合体系的继承关系,并深入对比 List 与 Set 的核心差异,配合 UML 类图和流程图,让你彻底搞懂:
- 集合框架的整体结构(
List、Set两大分支) ArrayList、LinkedList、Vector的区别与选型HashSet、TreeSet的底层实现与排序机制- 有序/无序 与 重复/不重复 的真正含义
- 如何根据业务场景选择合适的集合
2. 集合体系全景图(UML 类图)
Java 集合框架的根接口是 Collection,它派生出两大核心分支:List 和 Set。下面是简化的继承关系图:

说明:
List接口:有序、可重复、有索引。Set接口:无序(或特定顺序)、不可重复、无索引。- 虚线箭头表示实现接口,实线箭头表示继承。
3. List:有序可重复的序列
List 代表一个有序集合(Ordered Collection),即元素按照插入顺序排列,并且可以包含重复元素。每个元素都有一个整数索引,可以精确访问。
3.1 List 的核心特性
| 特性 | 描述 |
|---|---|
| 有序性 | 迭代顺序 = 插入顺序(除非手动排序)。 |
| 可重复性 | 允许存储 e1.equals(e2) == true 的元素。 |
| 索引访问 | 提供 get(int index)、set(int index, E element) 等方法。 |
| 遍历方式 | for 循环、增强 for、Iterator、ListIterator。 |
3.2 常用实现类对比
| 实现类 | 底层数据结构 | 随机访问 | 增删效率 | 线程安全 | 适用场景 |
|---|---|---|---|---|---|
ArrayList | 动态数组 | O(1) | 尾部 O(1),中间 O(n) | 否 | 查询多、增删少 |
LinkedList | 双向链表 | O(n) | 头部/尾部 O(1),中间 O(n) | 否 | 频繁头尾增删 |
Vector | 动态数组(同步) | O(1) | 尾部 O(1),中间 O(n) | 是(但落后) | 已过时,不建议使用 |
示例:
List<String> arrayList = new ArrayList<>();
arrayList.add("A"); // 尾部插入
arrayList.add(0, "B"); // 中间插入,元素后移
List<String> linkedList = new LinkedList<>();
linkedList.addFirst("head");
linkedList.addLast("tail");
选型建议:
- 大多数场景用
ArrayList,因为实际查询需求远多于中间插入。 - 如果需要线程安全的
List,使用Collections.synchronizedList()或CopyOnWriteArrayList(JUC 包)。
4. Set:不重复的集合
Set 接口代表一个不包含重复元素的集合。数学上集合的特性——互异性——在 Java 中通过 equals() 和 hashCode() 实现。
4.1 Set 的核心特性
| 特性 | 描述 |
|---|---|
| 不可重复性 | set.add(e) 时,如果 e 已存在(根据 equals 比较),则插入失败。 |
| 无序性(部分实现) | HashSet 不保证顺序;LinkedHashSet 按插入顺序;TreeSet 按自然顺序或比较器排序。 |
| 无索引 | 无法通过下标访问,只能通过迭代器或增强 for 遍历。 |
| 常用操作 | 并集、交集、差集等集合运算。 |
4.2 常用实现类对比
| 实现类 | 底层结构 | 排序 | 允许 null | 线程安全 | 使用场景 |
|---|---|---|---|---|---|
HashSet | HashMap | 无(哈希散列) | 一个 null | 否 | 最快去重,不关心顺序 |
LinkedHashSet | LinkedHashMap | 插入顺序 | 一个 null | 否 | 去重并保持插入顺序 |
TreeSet | TreeMap(红黑树) | 自然顺序/定制顺序 | 不允许 null(默认) | 否 | 需要排序去重 |
示例:
Set<String> hashSet = new HashSet<>();
hashSet.add("banana");
hashSet.add("apple");
hashSet.add("banana");
System.out.println(hashSet); // 无序,可能 [banana, apple]
Set<String> treeSet = new TreeSet<>();
treeSet.add("banana");
treeSet.add("apple");
System.out.println(treeSet); // [apple, banana] 按字典序
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("banana");
linkedHashSet.add("apple");
System.out.println(linkedHashSet); // [banana, apple] 插入顺序
注意:
HashSet的“无序” ≠ 随机顺序,而是指迭代顺序与插入顺序无关(依赖哈希分布)。- 自定义对象放入
HashSet/TreeSet时必须重写equals()和hashCode()(HashSet)或实现Comparable/提供Comparator(TreeSet)。
5. List vs Set:核心区别图解

5.1 对比表格
| 维度 | List | Set |
|---|---|---|
| 顺序 | 保证插入顺序 | 一般无序(除 LinkedHashSet、TreeSet) |
| 重复 | 允许重复 | 不允许重复 |
| 索引 | 有索引(通过整数访问) | 无索引,只能通过迭代器或增强 for |
| 常用实现 | ArrayList, LinkedList, Vector | HashSet, LinkedHashSet, TreeSet |
| 典型场景 | 需要保持顺序、可能需要重复、根据位置访问 | 去重、集合运算、自动排序 |
| 性能(查找) | 随机访问 O(1)(数组),链表 O(n) | HashSet O(1),TreeSet O(log n) |
| 允许 null | 允许任意多个 null | 最多一个 null(TreeSet 不允许) |
5.2 代码对比示例
// List 可以重复 List<Integer> list = new ArrayList<>(); list.add(1); list.add(1); System.out.println(list.size()); // 2 // Set 自动去重 Set<Integer> set = new HashSet<>(); set.add(1); set.add(1); System.out.println(set.size()); // 1
6. 如何选择 List 还是 Set?

决策要点:
- 如果元素必须按某个特定顺序(如用户操作日志),用
List。 - 如果业务要求元素唯一(如用户ID集合),用
Set。 - 如果既需要去重又需要保持插入顺序,用
LinkedHashSet。 - 如果既需要去重又需要自动排序,用
TreeSet。
7. 常见面试题
Q1: 为什么HashSet不保证顺序,而LinkedHashSet可以?
因为 HashSet 底层使用 HashMap,元素的存储位置由哈希码决定,插入顺序无法保留。LinkedHashSet 在 HashMap 基础上额外维护了一个双向链表,记录了插入顺序,因此迭代时按插入顺序输出。
Q2:ArrayList和LinkedList谁的内存占用更大?
LinkedList 每个节点需要存储前驱和后继引用,额外内存开销比 ArrayList 大。ArrayList 底层数组会有一定的容量预留(扩容策略),也可能存在空间浪费。总体而言,元素数量较多时 ArrayList 内存效率更高。
Q3:Vector已经过时,为什么还在某些旧项目中出现?
Vector 是 JDK 1.0 就存在的线程安全 List,方法使用 synchronized 修饰,性能较差。Java 1.2 引入 ArrayList 后,Vector 被标记为遗留类。如需线程安全,推荐使用 Collections.synchronizedList 或 CopyOnWriteArrayList。
Q4: 可以将Set转换为List吗?
可以,List 构造函数可以接收任何 Collection:
Set<String> set = new HashSet<>(Arrays.asList("A", "B"));
List<String> list = new ArrayList<>(set);
// 之后可对 list 排序、索引访问等
8. 总结
List:有序、可重复、有索引,适合需要按照插入顺序访问或根据位置操作的场景。Set:无序(或特定顺序)、不可重复、无索引,适合去重和集合运算。- 选择集合时,优先考虑元素是否唯一、顺序要求、访问模式。
- 熟悉底层数据结构(数组、链表、哈希表、红黑树)有助于理解性能差异。
记忆口诀:
List 排队可重复,Set 唯一不重复。
索引增删看实现,Hash 最快 Tree 排序。
到此这篇关于Java集合中List与Set的区别及体系全览的文章就介绍到这了,更多相关Java集合List与Set区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论