Java 集合框架- List
Java 集合框架- List:有序可重复的动态数据容器
在 Java 集合框架中,List 是最常使用的接口之一。它以**“有序、可重复”**为核心特性,为开发者提供了灵活的动态数据存储方案。无论是存储用户列表、操作日志还是实现复杂的业务队列,List 都扮演着不可或缺的角色。本文将从核心特性、典型实现到实战技巧,全面解析 List 接口及其实现类。
一、List 接口的核心特性
List 继承自 Collection 接口,是 Java 中**“有序集合”**的代表。其核心特性可概括为两点:
1. 有序性(Order)
- 插入顺序与存储顺序一致:元素在
List中的位置由插入顺序决定,后续插入的元素会排在前面元素之后。 - 支持索引访问:每个元素都有唯一的索引(从 0 开始),可通过索引直接定位元素(类似数组的
[index]操作)。
例如:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
System.out.println(list.get(0)); // 输出 "A"(索引 0 对应第一个插入的元素)
2. 可重复性(Duplication)
List 允许存储多个“相等”的元素(通过 equals() 方法判断)。例如:
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(10); // 允许重复添加
System.out.println(list.size()); // 输出 2(包含两个 10)
这与 Set 接口的“不可重复”特性形成鲜明对比,也让 List 更适合存储需要保留重复数据的场景(如用户操作日志中的重复行为)。
二、List 接口的核心方法
List 在 Collection 接口基础上,新增了一系列针对“索引操作”的方法,核心包括:
| 方法签名 | 功能描述 |
|---|---|
void add(int index, E element) | 在指定索引位置插入元素(后续元素自动后移) |
E get(int index) | 获取指定索引的元素 |
E set(int index, E element) | 替换指定索引的元素,返回被替换的旧元素 |
E remove(int index) | 删除指定索引的元素,返回被删除的元素 |
int indexOf(Object o) | 返回元素 o 第一次出现的索引(无则返回 -1) |
int lastIndexOf(Object o) | 返回元素 o 最后一次出现的索引(无则返回 -1) |
List<E> subList(int fromIndex, int toIndex) | 截取子列表(从 fromIndex 到 toIndex,左闭右开) |
这些方法体现了 List 对“有序性”的支持,尤其是 get(int index) 提供了高效的随机访问能力,这是 List 与其他集合(如 Set、Queue)的重要区别。
三、List 的典型实现类
List 接口有多个实现类,其中最常用的是 ArrayList、LinkedList 和 Vector。它们基于不同的数据结构实现,性能特性和适用场景各有侧重。
1. ArrayList:基于动态数组的高效随机访问
ArrayList 是 List 最常用的实现类,底层基于动态数组(可自动扩容的数组)存储元素。

核心特性:
- ArrayList 是集合的一个实现类:在 Java 集合框架中,ArrayList是List接口的实现类之一,属于集合体系的一部分,用于存储一组对象。
- 内部结构与长度特性:ArrayList内部封装了一个Object类型的数组(本质上是基于数组实现的),初始长度为 10。当元素数量超过当前容量时,它会自动扩容(通常是原容量的 1.5 倍),所以长度是可变的,不需要手动管理容量。
- 性能特点:
- 查询快:因为底层是数组,通过索引可以直接访问元素,所以随机查询(如get(int index))的效率很高。
- 增删慢:如果在集合中间位置进行添加或删除操作,需要移动大量元素(比如在索引i处添加元素,i之后的元素都要后移一位),所以增删操作效率较低;但如果是在集合尾部进行增删操作,效率还是比较高的。
- 线程安全性:ArrayList是线程不安全的,在多线程环境下如果没有额外的同步措施,可能会出现数据不一致的问题。如果需要线程安全的列表,可以考虑使用Vector或者Collections.synchronizedList(List)来包装ArrayList。
适用场景:
- 频繁读取元素(如查询操作多)。
- 元素数量可预估(初始化时指定容量可减少扩容次数)。
- 增删操作主要在尾部(无需移动元素)。
示例代码:
// 初始化时指定容量(推荐,减少扩容)
List<String> arrayList = new ArrayList<>(100);
arrayList.add("Java");
arrayList.add("Python");
arrayList.add("C++");
// 随机访问(高效)
String first = arrayList.get(0); // "Java"
// 尾部添加(高效)
arrayList.add("Go");
// 中间插入(低效,需移动元素)
arrayList.add(1, "JavaScript");
2. LinkedList:基于双向链表的高效增删
LinkedList 底层基于双向链表实现,每个元素通过“前驱”和“后继”指针关联。

核心特性:
- ArrayList 是集合的一个实现类:在 Java 集合框架中,LinkedList是List接口的实现类之一,属于集合体系的一部分,用于存储一组对象。
- 内部结构:LinkedList内部封装了一个双向链表(每个节点包含前驱节点引用、元素值、后继节点引用),通过节点之间的引用关系来维护元素的顺序。
- 性能特点:
- 查询快:在链表的任意位置(头部、中间、尾部)进行添加或删除元素时,只需修改节点的引用关系即可,不需要像数组那样移动大量元素,所以增删操作效率很高。
- 增删慢:如果要查询指定索引的元素,需要从链表的头节点(或尾节点)开始逐个遍历节点,直到找到目标索引,所以随机查询(如get(int index))的效率较低。
- 线程安全性:LinkedList是线程不安全的,在多线程环境下如果没有额外的同步措施,可能会出现数据不一致的问题。
适用场景:
- 频繁在首尾增删元素(如实现队列、栈)。
- 元素数量不确定,且中间元素操作较少。
示例代码:
List<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
// 头部添加(高效)
linkedList.add(0, "Head");
// 尾部删除(高效)
linkedList.remove(linkedList.size() - 1); // 删除 "B"
// 作为队列使用(FIFO)
Queue<String> queue = new LinkedList<>();
queue.offer("Task1"); // 入队
queue.poll(); // 出队(返回 "Task1")
3. Vector:线程安全的“老派”实现
Vector 是 Java 早期的 List 实现类,底层同样基于动态数组,但所有方法都被 synchronized 修饰,保证线程安全。

核心特性:
- 底层数据结构是:数组:Vector 底层基于数组实现,这一点和 ArrayList 类似。
- 有索引、查询快、增删慢:
- 因为基于数组,通过索引可直接访问元素,所以查询速度快;
- 但在增删元素时,需要移动大量数组元素(尤其是中间位置的增删),所以增删操作效率较低。
- 单线程,线程安全:Vector 是线程安全的集合,其方法大多被 synchronized 修饰,在单线程环境下使用时,由于线程安全的开销,性能会比 ArrayList 稍差;不过在多线程场景中,它能保证数据的一致性,而 ArrayList 是线程不安全的。
适用场景:
- 仅在低并发、需要线程安全的场景下使用(现在更推荐
Collections.synchronizedList(new ArrayList<>())或CopyOnWriteArrayList)。
示例代码:
List<Integer> vector = new Vector<>();
vector.add(1);
vector.add(2);
int value = vector.get(0); // 线程安全的访问
四、ArrayList 与 LinkedList 的核心区别
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 随机访问(get) | O(1)(高效) | O(n)(低效) |
| 中间插入/删除 | O(n)(需移动元素) | O(n)(需遍历定位,但移动成本低) |
| 首尾插入/删除 | O(1)(尾部高效,头部低效) | O(1)(高效) |
| 内存占用 | 连续空间,可能有冗余容量 | 非连续空间,额外存储指针 |
| 线程安全性 | 非线程安全 | 非线程安全 |
选择建议:
- 优先用
ArrayList:读多写少、需要随机访问。 - 优先用
LinkedList:写多(尤其是首尾操作)、无需随机访问。
五、List 的实战避坑指南
1. 避免在循环中使用 ArrayList 的 remove(int index)
ArrayList 的 remove(int index) 会导致后续元素前移,若在 for 循环中直接使用,可能跳过元素:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 错误示例:删除元素后索引变化,导致 "C" 被跳过
for (int i = 0; i < list.size(); i++) {
list.remove(i);
}
// 正确方式:使用迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
2. subList 不是独立的新集合
List.subList(from, to) 返回的子列表是原列表的视图(共享底层数据),修改子列表会影响原列表:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
List<Integer> subList = list.subList(1, 3); // 子列表 [2, 3]
subList.add(5);
System.out.println(list); // 输出 [1, 2, 3, 5, 4](原列表被修改)
若需独立子列表,需新建 ArrayList:
List<Integer> subList = new ArrayList<>(list.subList(1, 3));
3. 初始化 ArrayList 时指定容量
ArrayList 扩容会创建新数组并复制元素,频繁扩容影响性能。已知元素数量时,指定初始容量可优化:
// 已知约有 1000 个元素,直接初始化容量为 1000
List<User> users = new ArrayList<>(1000);
4. 线程安全问题
ArrayList 和 LinkedList 都是非线程安全的,多线程并发读写可能导致数据错乱或 ConcurrentModificationException。解决方式:
- 低并发:使用
Collections.synchronizedList(list)包装。 - 高并发(读多写少):使用
CopyOnWriteArrayList(写时复制,读无锁)。
六、总结
List 接口以“有序、可重复”为核心,通过 ArrayList、LinkedList 等实现类,满足了不同场景下的动态数据存储需求。选择 List 实现类时,需权衡随机访问效率和增删性能:
ArrayList适合读多写少、需要随机访问的场景;LinkedList适合频繁在首尾操作元素的场景。

更多推荐


所有评论(0)