在Java编程中,集合框架是我们最常用的工具之一。而List作为集合框架的核心接口,以其有序、可重复的特性,成为日常开发中的"常客"。无论是存储查询结果、管理用户列表,还是实现队列/栈等数据结构,List都扮演着重要角色。
本文将全面介绍List集合的特点、常用实现类、核心API以及实际应用场景,帮助你深入理解并灵活运用List集合。
一、List集合概述
1.1 什么是List?
List接口继承自Collection接口,是Java集合框架中的重要组成部分。它代表了一个有序的、可重复的元素集合,用户可以精确控制每个元素的插入位置,并通过整数索引访问元素。
1.2 List的核心特点
与Set、Map等其他集合类型相比,List具有三个鲜明特征:
| 特点 | 说明 | 示例 |
|---|---|---|
| 有序性 | 元素存入的顺序与取出的顺序一致 | 存入"A","B","C",取出时仍然是"A","B","C" |
| 可重复 | 允许存储相同的元素 | 可以添加两个相同的"hello" |
| 带索引 | 可以通过索引精确操作元素 | list.get(0)、list.remove(2) |
1.3 集合与数组的区别
理解List之前,先明确集合与数组的本质差异:
| 对比维度 | 数组 | 集合 |
|---|---|---|
| 长度 | 固定不变 | 动态可变 |
| 存储类型 | 可存基本类型和引用类型 | 只能存引用类型 |
| 访问方式 | 通过索引 | 迭代器、增强for等多种方式 |
二、List的主要实现类
List接口有多个实现类,其中最常用的是ArrayList、LinkedList和Vector。它们各有特点,适用于不同场景。
2.1 ArrayList:动态数组实现
底层数据结构:Object[]数组
特点:
-
查询效率高(通过索引直接定位,时间复杂度O(1))
-
增删效率一般(特别是中间位置插入/删除,需要移动元素)
-
线程不安全
适用场景:频繁的随机访问操作、尾部插入为主
扩容机制:
-
默认初始容量为10
-
当容量不足时,新容量 = 旧容量 × 1.5
-
JDK 7:构造时直接创建长度为10的数组(饿汉式)
-
JDK 8:构造时创建空数组,第一次add时才创建长度为10的数组(懒汉式,节省内存)
2.2 LinkedList:双向链表实现
底层数据结构:双向链表
java
private static class Node<E> {
E item; // 当前元素
Node<E> next; // 后一个节点引用
Node<E> prev; // 前一个节点引用
}
特点:
-
查询效率一般(需要遍历查找,时间复杂度O(n))
-
增删效率高(只需修改节点引用)
-
线程不安全
-
提供了丰富的首尾操作方方法(
addFirst、addLast、removeFirst等)
适用场景:频繁的插入/删除操作、实现队列或栈
2.3 Vector:线程安全的动态数组
特点:
-
与ArrayList类似,基于数组实现
-
线程安全(方法用synchronized修饰)
-
扩容机制不同:默认扩容为原数组的2倍
-
性能比ArrayList低
适用场景 :需要线程安全的场景(但现代Java并发首选CopyOnWriteArrayList)
2.4 实现类对比一览
| 特性 | ArrayList | LinkedList | Vector |
|---|---|---|---|
| 数据结构 | 动态数组 | 双向链表 | 动态数组 |
| 随机访问 | O(1) | O(n) | O(1) |
| 插入/删除 | O(n)(尾部O(1)) | O(1)(头尾) | O(n) |
| 线程安全 | 否 | 否 | 是 |
| 扩容倍数 | 1.5倍 | 不需扩容 | 2倍 |
三、List常用API详解
List除了继承Collection的方法外,还增加了大量索引相关的方法。
3.1 核心方法
java
// 创建List集合(多态写法)
List<String> list = new ArrayList<>();
// 1. 添加元素
list.add("Java"); // 尾部添加
list.add(1, "Python"); // 指定位置添加
// 2. 获取元素
String lang = list.get(0); // 获取索引0的元素
// 3. 修改元素
list.set(1, "C++"); // 替换索引1的元素
// 4. 删除元素
list.remove(0); // 删除索引0的元素
list.remove("Java"); // 删除指定元素(首次出现)
// 5. 其他操作
int size = list.size(); // 集合长度
boolean exists = list.contains("Java"); // 是否包含
int index = list.indexOf("C++"); // 查找元素位置
boolean isEmpty = list.isEmpty(); // 是否为空
3.2 LinkedList特有方法
LinkedList提供了丰富的首尾操作方法,使其非常适合实现队列或栈:
java
LinkedList<String> linkedList = new LinkedList<>();
// 首部操作
linkedList.addFirst("First"); // 头部添加
linkedList.push("Push"); // 等效于addFirst
String first = linkedList.getFirst(); // 获取头部
String removedFirst = linkedList.removeFirst(); // 移除头部
String pop = linkedList.pop(); // 等效于removeFirst
// 尾部操作
linkedList.addLast("Last"); // 尾部添加
String last = linkedList.getLast(); // 获取尾部
String removedLast = linkedList.removeLast(); // 移除尾部
// 判断空
boolean empty = linkedList.isEmpty();
四、List的遍历方式
List支持多种遍历方式,各有适用场景:
4.1 六种遍历方法
java
List<String> list = Arrays.asList("A", "B", "C");
// 1. 普通for循环(带索引)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 2. 增强for循环(最简洁)
for (String item : list) {
System.out.println(item);
}
// 3. 迭代器(Iterator)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
// iterator.remove(); // 可在遍历时安全删除
}
// 4. 列表迭代器(ListIterator)- 支持双向遍历和修改
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
String item = listIterator.next();
if ("B".equals(item)) {
listIterator.remove(); // 删除
listIterator.add("D"); // 插入
listIterator.set("E"); // 替换
}
}
// 5. forEach(Lambda表达式)
list.forEach(item -> System.out.println(item));
// 6. Stream流
list.stream().forEach(System.out::println);
list.parallelStream().forEach(System.out::println); // 并行流
4.2 并发修改异常
重要提醒 :使用迭代器遍历时,不能 通过集合对象修改元素(如list.add()),否则会抛出ConcurrentModificationException。
java
// 错误示例:会抛出异常
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("B".equals(it.next())) {
list.add("D"); // 并发修改异常!
}
}
// 正确做法:使用迭代器的remove方法,或使用普通for循环
for (int i = 0; i < list.size(); i++) {
if ("B".equals(list.get(i))) {
list.add("D"); // 普通for循环允许修改
}
}
五、线程安全的List
5.1 实现线程安全的三种方式
java
// 方式1:使用Collections工具类包装
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
// 方式2:使用Vector(古老实现,不推荐)
List<String> vector = new Vector<>();
// 方式3:使用CopyOnWriteArrayList(推荐)
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
5.2 CopyOnWriteArrayList原理
CopyOnWriteArrayList是并发包下的线程安全List,采用读写分离策略:
-
写操作:加锁(ReentrantLock),复制新数组,修改后替换原数组引用
-
读操作:无锁,直接读取(可能读到旧数据,但保证最终一致性)
-
优点:读操作极高并发,适合读多写少场景
-
缺点:写操作开销大(复制数组),数据弱一致性
六、实际应用场景与选择建议
6.1 场景对比
| 场景 | 推荐实现 | 原因 |
|---|---|---|
| 频繁随机访问(如排行榜) | ArrayList | 索引访问O(1) |
| 尾部插入为主(如日志收集) | ArrayList | 尾部插入O(1) |
| 频繁中间插入/删除 | LinkedList | 只需修改节点引用 |
| 队列(FIFO) | LinkedList | 提供了removeFirst()等方法 |
| 栈(LIFO) | LinkedList | 提供了push()/pop()方法 |
| 多线程环境读多写少 | CopyOnWriteArrayList | 读写分离,并发度高 |
| 多线程环境读写频繁 | Collections.synchronizedList() | 平衡性能与安全 |
6.2 简单案例:用户管理系统
java
public class UserManager {
private List<String> users;
public UserManager() {
// 根据场景选择实现类
users = new ArrayList<>(); // 查询为主
// users = new LinkedList<>(); // 插入删除为主
}
public void addUser(String user) {
users.add(user);
}
public void removeUser(String user) {
users.remove(user);
}
public void printUsers() {
users.forEach(System.out::println);
}
public static void main(String[] args) {
UserManager manager = new UserManager();
manager.addUser("Alice");
manager.addUser("Bob");
manager.addUser("Charlie");
manager.printUsers();
manager.removeUser("Bob");
System.out.println("--- 删除后 ---");
manager.printUsers();
}
}
七、总结
List集合是Java开发中最基础、最常用的数据结构之一。掌握List,需要理解:
-
三大特点:有序、可重复、带索引
-
两大主力实现:ArrayList(数组,查询快)和LinkedList(链表,增删快)
-
一大线程安全选择:CopyOnWriteArrayList(读多写少场景)
-
多种遍历方式:根据需求选择合适的遍历方法