1. 引言
List 是 Java 集合框架中最常用的接口之一,它表示一个有序、可重复的元素集合。无论是日常开发还是面试考察,List 都是绕不开的核心知识点。本文将从接口定义、常用实现类、遍历方式到实战注意事项,系统梳理 Java List 接口的方方面面。
2. List 接口概述
List 接口继承自 Collection 接口,位于 java.util 包中。它允许存储重复元素,并且每个元素都有对应的索引位置,可以通过索引精确访问元素。与 Set 不同,List 保持了元素的插入顺序。
List 接口的主要特点包括:
- 有序性:元素按照插入顺序排列,可通过索引访问。
- 可重复:允许存储相同的元素。
- 允许 null:大多数实现类允许存储 null 值。
- 索引访问:支持通过下标快速定位元素。
3. 常用实现类
List 接口有多个实现类,其中最常用的是 ArrayList、LinkedList 和 Vector。三者底层结构不同,适用场景也各有侧重。
3.1 ArrayList
ArrayList 基于动态数组实现,支持随机访问,查询效率高,但插入和删除元素时可能需要移动后续元素,性能相对较低。它是日常开发中使用最频繁的 List 实现。
java
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("Go");
System.out.println(list.get(1)); // 输出 Python
3.2 LinkedList
LinkedList 基于双向链表实现,插入和删除元素效率高,但随机访问性能较差。它同时实现了 List 和 Deque 接口,因此也可以作为队列或双端队列使用。
java
List<Integer> numbers = new LinkedList<>();
numbers.add(10);
numbers.add(20);
numbers.addFirst(5); // 头部插入
System.out.println(numbers); // 输出 [5, 10, 20]
3.3 Vector
Vector 是早期版本的线程安全实现,方法使用 synchronized 修饰。由于同步开销较大,在单线程场景下性能不如 ArrayList,目前已经较少使用。若需要线程安全的 List,推荐使用 Collections.synchronizedList 或 CopyOnWriteArrayList。
4. 常用方法
List 接口提供了丰富的方法用于元素操作,下面按功能分类介绍。
4.1 添加元素
- add(E e):在列表末尾追加元素。
- add(int index, E e):在指定索引位置插入元素。
- addAll(Collection c):将另一个集合的所有元素追加到末尾。
4.2 删除元素
- remove(int index):删除指定索引位置的元素。
- remove(Object o):删除第一个匹配的指定元素。
- clear():清空列表中的所有元素。
4.3 查询与修改
- get(int index):获取指定索引位置的元素。
- set(int index, E e):替换指定索引位置的元素。
- size():返回列表中的元素个数。
- contains(Object o):判断列表是否包含指定元素。
- indexOf(Object o):返回指定元素首次出现的索引。
5. 遍历方式
List 的遍历方式多种多样,常见的有 for 循环、增强 for、迭代器和 Stream 流。不同方式在简洁性和灵活性上各有优劣。
5.1 普通 for 循环
java
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
5.2 增强 for 循环
java
for (String item : list) {
System.out.println(item);
}
5.3 迭代器遍历
java
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
5.4 Stream 流遍历
java
list.stream().forEach(System.out::println);
6. 排序与去重
对 List 进行排序和去重是开发中的高频操作。排序可以使用 Collections.sort 或 List.sort 方法,去重则常借助 Stream 的 distinct 方法。
java
List<Integer> scores = new ArrayList<>(Arrays.asList(88, 92, 75, 88, 60));
// 排序
scores.sort(Integer::compareTo);
System.out.println(scores); // 输出 [60, 75, 88, 88, 92]
// 去重
List<Integer> unique = scores.stream().distinct().collect(Collectors.toList());
System.out.println(unique); // 输出 [60, 75, 88, 92]
7. 线程安全与并发场景
ArrayList 和 LinkedList 都不是线程安全的。在多线程环境下,如果多个线程同时修改同一个 List,可能引发数据不一致或 ConcurrentModificationException。常用的解决方案有以下几种:
- Collections.synchronizedList:对 List 进行同步包装。
- CopyOnWriteArrayList:读写分离,适合读多写少的场景。
- Vector:传统线程安全实现,但性能较差。
8. 常见问题与注意事项
在使用 List 时,有几个容易踩坑的地方需要特别留意。
8.1 遍历时删除元素
在增强 for 循环中直接调用 remove 方法会抛出 ConcurrentModificationException,应使用迭代器的 remove 方法或 Stream 过滤。
java
// 错误示例
for (String item : list) {
if ("Java".equals(item)) {
list.remove(item); // 抛出异常
}
}
// 正确示例
list.removeIf("Java"::equals);
8.2 Arrays.asList 的陷阱
Arrays.asList 返回的是固定长度的列表,不能调用 add 和 remove 方法,否则会抛出 UnsupportedOperationException。
java
List<String> fixed = Arrays.asList("A", "B");
fixed.add("C"); // 抛出 UnsupportedOperationException
8.3 索引越界
访问不存在的索引会抛出 IndexOutOfBoundsException,建议在访问前先判断索引范围。
9. 总结
List 是 Java 集合框架中最重要的接口之一,掌握它的实现原理、常用方法和遍历方式,是写出高质量 Java 代码的基础。在实际开发中,应根据具体场景选择合适的实现类:查询频繁选 ArrayList,插入删除频繁选 LinkedList,并发场景则考虑线程安全方案。希望本文能帮助你更系统地理解 Java List 接口。