1. 基本概念与接口
Java List 是 Java 集合框架中的一个关键接口,继承自 Collection,并且具备有序和可重复元素的特性。它允许通过索引进行随机访问,便于在任意位置插入、移除或修改元素,适合需要按照元素顺序处理数据的场景。核心属性包括有序性、位置访问能力,以及与 List接口 的其他方法密切配合的特性。
在实现层面,常见的 List 实现包括 ArrayList、LinkedList、以及线程安全的 CopyOnWriteArrayList 等。不同实现之间的差异导致了不同的时间复杂度和适用场景:随机访问能力强 的 ArrayList,频繁插入删除在头尾高效 的 LinkedList,以及在并发读多写少场景下的 CopyOnWriteArrayList。了解这些差异有助于从入门到实战选择合适的 List 实现。
下面给出一个简单示例,演示如何创建 List、添加元素以及通过索引访问元素的基本用法:
import java.util.*;
public class ListQuickStart {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add(1, "C"); // 在指定位置插入
System.out.println(list.get(0)); // 输出 A
System.out.println(list); // 输出 [A, C, B]
}
}
2. 常用实现及对比
ArrayList 是最常用的 List 实现,底层采用动态数组结构,提供 O(1) 的随机访问,但在中间位置插入或删除元素时需要移动大量元素,因此在高频插入/删除的场景下性能不如 LinkedList。适合“读多写少”的场景以及需要快速按下标访问的操作。
LinkedList 使用双向链表结构,头尾插入/删除通常是 O(1) 的,若要在中间位置移动指针,需要通过遍历来定位,时间复杂度为 O(n)。它在需要频繁进行头尾操作或大量中间插入/删除时表现更佳。对于多线程环境,应该考虑并发控制或使用并发列表实现。
CopyOnWriteArrayList 提供线程安全的读多写少场景,在写操作时会复制底层数组,因此写操作成本较高,但读取操作几乎无锁,适合高并发且写操作较少的场景。理解这些实现差异有助于在不同业务场景中做出正确的选择。
示例对比代码:创建不同实现并执行简单增删查操作:
import java.util.*;
public class ListImplementations {
public static void main(String[] args) {
List<String> aList = new ArrayList<>();
aList.add("One");
aList.add("Two");
List<String> lList = new LinkedList<>();
lList.add("Alpha");
lList.add("Beta");
List<String> cList = new CopyOnWriteArrayList<>();
cList.add("X");
cList.add("Y");
System.out.println(aList);
System.out.println(lList);
System.out.println(cList);
}
}
3. 基本操作(增删改查)
对 List 的基本操作包括新增、删除、修改和查询等常用行为。在实际编码中,add、remove、get、set、size 等方法是日常使用的核心。
新增与插入:使用 add(E element) 在末尾追加,或 add(int index, E element) 在指定位置插入;两者的时间复杂度取决于实现,ArrayList 在尾部追加通常是常数时间,而在中间插入需要移动元素。
删除与修改:按下标删除 remove(int index),按对象删除 remove(Object o);修改元素通过 set(int index, E element)。查询元素通过 get(int index) 或 contains、indexOf、lastIndexOf。
演示常见操作的代码片段如下:
import java.util.*;
public class ListCRUD {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
// 增
nums.add(10);
nums.add(20);
nums.add(1, 15); // 在下标 1 处插入
// 查
int first = nums.get(0);
boolean has20 = nums.contains(20);
// 改
nums.set(2, 25);
// 删
nums.remove(Integer.valueOf(10));
nums.remove(0);
System.out.println(nums); // 输出 [15, 25]
System.out.println("容量大小: " + nums.size());
}
}
4. 高级操作与性能优化
List 的高级操作包括子集合、排序、搜索以及批量操作等。合理使用可以显著提升代码的表达力与性能。
子集合:subList(fromIndex, toIndex) 返回的并非独立的新集合,而是对原 List 的一个视图,修改视图会直接影响原始 List。这个特性在处理局部视图时非常有用,但要谨慎以避免意外副作用。
排序与搜索:对 List 使用 Collections.sort(list, comparator) 进行自定义排序,Collections.binarySearch(list, key) 进行二分查找时要求列表已按升序排序。使用前应确保排序条件正确。
性能优化思路包括:在已知元素数量时通过带初始容量的构造函数初始化 List(如 new ArrayList<>(initialCapacity)),减少自动扩容带来的代价;避免在高并发场景下频繁修改同一个 List,而是使用并发友好的实现或对访问进行同步控制。
示例:对 List 进行子 List、排序与二分查找的操作:
import java.util.*;
public class ListAdvanced {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
nums.addAll(Arrays.asList(3, 1, 4, 1, 5, 9, 2));
// 子集合视图
List<Integer> sub = nums.subList(1, 5);
System.out.println(sub); // [1, 4, 1, 5]
// 排序
Collections.sort(nums);
System.out.println(nums); // [1, 1, 2, 3, 4, 5, 9]
// 二分查找
int idx = Collections.binarySearch(nums, 4);
System.out.println("元素 4 的下标: " + idx);
}
}
5. 与 Java Streams 的结合
将 List 与 Java Streams 结合使用,可以实现更简洁、可维护的数据处理流程。通过 stream()、map()、filter()、collect() 等操作,能以声明式方式实现过滤、转换、聚合等任务。
典型用法包括对 List 进行筛选、映射、去重以及汇总统计等,代码可读性和可维护性明显提升。
示例:对整数列表进行筛选、平方映射并收集为新 List:
import java.util.*;
import java.util.stream.*;
public class ListStreams {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.filter(n -> n % 2 == 1) // 仅保留奇数
.map(n -> n * n) // 平方映射
.collect(Collectors.toList());
System.out.println(squares); // 输出 [1, 9, 25]
}
}
6. 常见坑与最佳实践
在开发过程中,List 的使用容易踩到一些坑。通过遵循最佳实践,可以避免常见错误并提升代码鲁棒性。
迭代时修改需小心:在使用 for-each 循环遍历 List 时直接调用 remove 会抛出并发修改异常。应使用 Iterator 的 remove(),或使用 removeIf()、或将修改操作放在独立的阶段处理。
并发修改与线程安全:在多线程环境下直接对 List 进行并发修改会带来不可预期的行为。若需要并发读写,请考虑 CopyOnWriteArrayList、或将 List 包装为 synchronizedList,或使用其他并发集合实现。
空元素与 equals/hashCode:List 通常允许 null 元素,但对元素进行去重、查找等操作时,正确实现元素类的 equals 与 hashCode 非常关键。否则可能导致检索异常或错判。
示例:在遍历时安全地删除满足条件的元素:
import java.util.*;
public class ListSafeRemoval {
public static void main(String[] args) {
List<String> names = new ArrayList<>(Arrays.asList("Ada", "Bob", "Cathy", "Derek"));
// 使用Iterator安全删除
for (Iterator<String> it = names.iterator(); it.hasNext();) {
String s = it.next();
if (s.length() > 4) {
it.remove();
}
}
System.out.println(names); // 输出 [Ada, Bob]
// 或直接使用 removeIf
names.removeIf(s -> s.length() > 3);
System.out.println(names); // 输出 [Ada, Bob]
}
}


