1. Java List基础概念与核心特性
Java中的List接口是集合框架中最基础也最常用的数据结构之一,它代表一个有序的元素集合。与Set不同,List允许存储重复元素,并且维护着严格的插入顺序。在实际开发中,ArrayList和LinkedList是最常见的两种实现。
List的核心特性主要体现在三个方面:首先是有序性,每个元素都有确定的索引位置;其次是可重复性,允许存储相同的对象引用;最后是元素访问的灵活性,既可以通过索引直接访问,也可以使用迭代器遍历。
注意:虽然List允许null元素存在,但在实际业务中应尽量避免,因为这会增加NPE风险并影响代码可读性。
1.1 List接口的体系结构
Java集合框架中List接口的继承关系如下:
code复制Collection ← List ← (ArrayList, LinkedList, Vector, Stack)
ArrayList基于动态数组实现,查询效率高(O(1))但插入删除性能较差(O(n));LinkedList基于双向链表实现,插入删除快(O(1))但随机访问慢(O(n))。Vector是线程安全版本,但性能较差,现代Java开发中更推荐使用Collections.synchronizedList()或CopyOnWriteArrayList。
1.2 初始化List的多种方式
java复制// 最常用的ArrayList初始化
List<String> list1 = new ArrayList<>();
// 使用Arrays工具类初始化
List<String> list2 = Arrays.asList("A", "B", "C");
// Java9+的工厂方法
List<String> list3 = List.of("A", "B", "C");
// 双括号初始化(匿名内部类方式)
List<String> list4 = new ArrayList<String>() {{
add("A");
add("B");
}};
提示:Arrays.asList()返回的是固定大小的列表,不能进行add/remove操作。List.of()返回的是不可变列表,修改会抛出UnsupportedOperationException。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. List核心操作方法详解
2.1 基础CRUD操作
添加元素:
java复制List<String> fruits = new ArrayList<>();
fruits.add("Apple"); // 尾部添加
fruits.add(0, "Banana"); // 指定位置插入
fruits.addAll(Arrays.asList("Orange", "Grape")); // 批量添加
删除元素:
java复制fruits.remove("Apple"); // 按对象删除
fruits.remove(0); // 按索引删除
fruits.removeAll(Arrays.asList("Orange", "Grape")); // 批量删除
fruits.clear(); // 清空列表
修改元素:
java复制fruits.set(1, "Mango"); // 替换指定位置元素
查询元素:
java复制String first = fruits.get(0); // 获取指定位置元素
int index = fruits.indexOf("Mango"); // 查找元素索引
boolean exists = fruits.contains("Apple"); // 判断包含
2.2 批量操作与集合运算
List支持多种集合运算,这些操作在处理数据集合时非常实用:
java复制List<Integer> listA = new ArrayList<>(Arrays.asList(1, 2, 3));
List<Integer> listB = new ArrayList<>(Arrays.asList(2, 3, 4));
// 并集
listA.addAll(listB); // [1,2,3,2,3,4]
// 交集
listA.retainAll(listB); // [2,3]
// 差集
listA.removeAll(listB); // [1]
// 判断子集
boolean isSubset = listA.containsAll(Arrays.asList(1, 2));
注意:这些批量操作会直接修改原列表,如果需要保留原列表,应先创建副本:
java复制List<Integer> union = new ArrayList<>(listA); union.addAll(listB);
2.3 迭代遍历的多种方式
1. 基本for循环:
java复制for (int i = 0; i < fruits.size(); i++) {
System.out.println(fruits.get(i));
}
2. 增强for循环:
java复制for (String fruit : fruits) {
System.out.println(fruit);
}
3. 使用Iterator:
java复制Iterator<String> it = fruits.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
4. Java8+的forEach:
java复制fruits.forEach(System.out::println);
// 或带索引的遍历
IntStream.range(0, fruits.size())
.forEach(i -> System.out.println(i + ":" + fruits.get(i)));
5. ListIterator双向遍历:
java复制ListIterator<String> lit = fruits.listIterator(fruits.size());
while (lit.hasPrevious()) {
System.out.println(lit.previous());
}
提示:在遍历过程中修改列表结构(add/remove)时,必须使用Iterator的remove()方法,否则会抛出ConcurrentModificationException。
3. List高级操作与Java8+特性
3.1 排序与比较操作
基本排序:
java复制List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
Collections.sort(numbers); // 升序排序
Collections.sort(numbers, Collections.reverseOrder()); // 降序排序
Java8的Stream排序:
java复制List<Integer> sorted = numbers.stream()
.sorted()
.collect(Collectors.toList());
自定义对象排序:
java复制class Person {
String name;
int age;
// getters/setters
}
List<Person> people = ...;
// 传统方式
people.sort(new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return Integer.compare(p1.getAge(), p2.getAge());
}
});
// Java8方式
people.sort(Comparator.comparingInt(Person::getAge));
people.sort(Comparator.comparing(Person::getName).thenComparingInt(Person::getAge));
3.2 Stream API的妙用
Java8引入的Stream API为List操作带来了革命性的变化:
过滤与转换:
java复制List<String> filtered = fruits.stream()
.filter(f -> f.startsWith("A"))
.map(String::toUpperCase)
.collect(Collectors.toList());
分组与统计:
java复制Map<Integer, List<Person>> peopleByAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge));
long count = fruits.stream().count();
int totalAge = people.stream().mapToInt(Person::getAge).sum();
Optional<Person> oldest = people.stream().max(Comparator.comparingInt(Person::getAge));
并行处理:
java复制List<String> parallelProcessed = fruits.parallelStream()
.map(String::toLowerCase)
.collect(Collectors.toList());
3.3 不可变列表与防御式编程
创建不可变列表是防御式编程的重要实践:
java复制// Java9之前
List<String> immutable1 = Collections.unmodifiableList(new ArrayList<>(fruits));
// Java9+
List<String> immutable2 = List.of("A", "B", "C");
// 尝试修改会抛出UnsupportedOperationException
immutable2.add("D");
重要:Collections.unmodifiableList()返回的是原列表的视图,如果原列表被修改,不可变视图也会随之变化。而List.of()创建的是真正的不可变副本。
4. 性能优化与最佳实践
4.1 初始化容量优化
ArrayList基于数组实现,默认初始容量为10,当元素数量超过容量时会自动扩容(约1.5倍)。频繁扩容会影响性能:
java复制// 预估有1000个元素时
List<String> optimizedList = new ArrayList<>(1000);
实测表明,预先设置合理初始容量可以显著提升性能:
- 添加100万元素,无初始容量:约500ms
- 添加100万元素,初始容量100万:约200ms
4.2 选择正确的List实现
| 场景 | 推荐实现 | 原因 |
|---|---|---|
| 频繁随机访问 | ArrayList | O(1)访问时间 |
| 频繁插入删除 | LinkedList | O(1)插入删除 |
| 多线程环境 | CopyOnWriteArrayList | 线程安全 |
| 需要栈结构 | ArrayDeque | 比Stack更高效 |
4.3 常见陷阱与解决方案
1. 并发修改异常:
java复制// 错误示例
for (String fruit : fruits) {
if (fruit.equals("Apple")) {
fruits.remove(fruit); // 抛出ConcurrentModificationException
}
}
// 正确做法
Iterator<String> it = fruits.iterator();
while (it.hasNext()) {
if (it.next().equals("Apple")) {
it.remove(); // 使用迭代器的remove方法
}
}
// Java8+方式
fruits.removeIf(f -> f.equals("Apple"));
2. 浅拷贝问题:
java复制List<Person> copy = new ArrayList<>(people); // 只是浅拷贝
people.get(0).setName("Changed"); // 会影响copy中的对应对象
// 深拷贝解决方案
List<Person> deepCopy = people.stream()
.map(p -> new Person(p.getName(), p.getAge()))
.collect(Collectors.toList());
3. 大数据量分页:
java复制// 低效方式(全量加载)
List<Data> allData = getHugeDataFromDB();
List<Data> page = allData.subList(start, end);
// 高效方式(按需加载)
List<Data> page = getDataFromDBWithPagination(pageNum, pageSize);
4.4 调试与性能分析技巧
查看List实现类:
java复制System.out.println(fruits.getClass().getName());
// 输出:java.util.ArrayList
内存分析:
使用JVisualVM或YourKit等工具分析List内存占用,特别注意:
- 底层数组的冗余空间(ArrayList的trimToSize())
- 元素对象的实际内存占用
性能测试建议:
java复制// 使用JMH进行微基准测试
@Benchmark
public void testArrayListAdd(Blackhole bh) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
bh.consume(list);
}
5. 实际应用案例
5.1 数据转换处理
java复制// CSV字符串转List
String csv = "Apple,Banana,Orange";
List<String> items = Arrays.asList(csv.split(","));
// List转Map
Map<String, Integer> fruitMap = fruits.stream()
.collect(Collectors.toMap(
f -> f,
f -> f.length()
));
5.2 分页查询实现
java复制public <T> List<T> getPage(List<T> sourceList, int page, int pageSize) {
if (pageSize <= 0 || page <= 0) {
throw new IllegalArgumentException("页码和页大小必须大于0");
}
int fromIndex = (page - 1) * pageSize;
if (sourceList == null || sourceList.size() <= fromIndex) {
return Collections.emptyList();
}
return sourceList.subList(fromIndex, Math.min(fromIndex + pageSize, sourceList.size()));
}
5.3 多条件筛选
java复制List<Person> filteredPeople = people.stream()
.filter(p -> p.getAge() > 18)
.filter(p -> p.getName().startsWith("A"))
.sorted(Comparator.comparing(Person::getAge))
.collect(Collectors.toList());
5.4 与数组的转换
java复制// List转数组
String[] array = fruits.toArray(new String[0]);
// 数组转List
List<String> list = Arrays.asList(array); // 固定大小
List<String> modifiableList = new ArrayList<>(Arrays.asList(array));
在实际项目中,合理使用List可以极大提高开发效率。我个人的经验是:对于查询密集型操作优先选择ArrayList;对于频繁修改的场景考虑LinkedList;多线程环境下务必考虑线程安全方案。另外,Java8的Stream API虽然强大,但要注意它不总是性能最优的选择,对于简单操作,传统循环可能更高效。
