1. List集合基础与核心特性解析
Java集合框架中的List接口是最常用的数据结构之一,它代表一个有序的元素集合(也称为序列)。与数组不同,List的长度是可变的,并且提供了丰富的操作方法。在实际开发中,ArrayList和LinkedList是最常见的两种实现。
1.1 ArrayList底层实现原理
ArrayList基于动态数组实现,其内部使用Object[]数组存储元素。当添加元素导致容量不足时,会自动进行扩容操作:
java复制// JDK中的扩容核心代码
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
重要提示:ArrayList的默认初始容量为10,频繁扩容会影响性能。如果能预估数据量,建议通过构造函数指定初始容量。
1.2 LinkedList的链表结构
LinkedList采用双向链表实现,每个节点(Node)包含三个字段:
- E item:存储的元素
- Node
next:指向下一个节点 - Node
prev:指向前一个节点
这种结构使得LinkedList在头部和尾部插入/删除元素的时间复杂度为O(1),但随机访问需要遍历链表,时间复杂度为O(n)。
2. List核心API与性能对比
2.1 常用操作方法对比
| 操作 | ArrayList | LinkedList |
|---|---|---|
| get(int index) | O(1) | O(n) |
| add(E element) | 均摊O(1) | O(1) |
| add(int index, E element) | O(n) | O(n) |
| remove(int index) | O(n) | O(n) |
2.2 迭代器使用要点
List的iterator()和listIterator()方法返回的迭代器都支持快速失败(fail-fast)机制:
java复制List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String s = it.next();
if(s.equals("B")) {
list.remove(s); // 抛出ConcurrentModificationException
}
}
正确做法:使用迭代器的remove()方法修改集合
3. 泛型深度解析与类型擦除
3.1 泛型基本语法
泛型类定义示例:
java复制public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
3.2 类型擦除原理
Java泛型是通过类型擦除实现的,编译后泛型类型信息会被擦除。例如:
java复制List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// 编译后都会变成原始类型List
System.out.println(stringList.getClass() == intList.getClass()); // 输出true
3.3 通配符使用场景
- 上界通配符:
<? extends T>表示T或T的子类 - 下界通配符:
<? super T>表示T或T的父类 - 无界通配符:
<?>表示任意类型
java复制// 上界通配符示例
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
4. List与泛型结合实践
4.1 类型安全的集合
使用泛型可以避免类型转换错误:
java复制// 非泛型写法(可能引发ClassCastException)
List rawList = new ArrayList();
rawList.add("hello");
Integer num = (Integer) rawList.get(0); // 运行时错误
// 泛型写法(编译时检查)
List<String> safeList = new ArrayList<>();
safeList.add("hello");
// safeList.add(123); // 编译错误
4.2 自定义泛型方法
java复制public static <T> void copyList(List<? super T> dest, List<? extends T> src) {
for (T item : src) {
dest.add(item);
}
}
4.3 不可变集合创建
Java 9+提供了方便的工厂方法创建不可变集合:
java复制List<String> immutableList = List.of("A", "B", "C");
// immutableList.add("D"); // 抛出UnsupportedOperationException
5. 性能优化与最佳实践
5.1 ArrayList优化技巧
-
预分配容量:对于已知大小的集合,构造时指定初始容量
java复制List<Integer> list = new ArrayList<>(1000); -
批量添加使用addAll()而非循环add()
-
使用ensureCapacity()提前扩容(适用于JDK1.8+)
5.2 LinkedList适用场景
- 频繁在头部/尾部插入删除元素
- 实现队列或双端队列结构
- 不需要随机访问的场景
5.3 泛型编程建议
-
尽量使用接口类型声明变量:
java复制List<String> list = new ArrayList<>(); -
避免使用原始类型(raw type)
-
合理使用
@SuppressWarnings("unchecked")注解
6. 常见问题排查
6.1 ConcurrentModificationException
现象:遍历集合时修改集合内容导致异常
解决方案:
- 使用迭代器的remove()方法
- 使用CopyOnWriteArrayList(线程安全场景)
- 遍历前创建副本
6.2 泛型数组创建问题
错误示例:
java复制T[] array = new T[10]; // 编译错误
正确做法:
java复制@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[10];
6.3 类型擦除带来的限制
问题:无法在运行时获取泛型类型信息
解决方案:
- 通过Class对象传递类型信息
- 使用TypeToken(Gson等库提供)
7. 高级应用场景
7.1 类型安全的异构容器
java复制public class TypeSafeContainer {
private Map<Class<?>, Object> map = new HashMap<>();
public <T> void put(Class<T> type, T instance) {
map.put(Objects.requireNonNull(type), type.cast(instance));
}
public <T> T get(Class<T> type) {
return type.cast(map.get(type));
}
}
7.2 泛型与反射结合
java复制public static <T> List<T> createList(Class<T> clazz) throws Exception {
List<T> list = new ArrayList<>();
// 使用反射创建实例并添加到列表
for(int i=0; i<5; i++) {
T instance = clazz.getDeclaredConstructor().newInstance();
list.add(instance);
}
return list;
}
7.3 Java 8 Stream与泛型
java复制public static <T> List<T> filterByType(List<?> list, Class<T> type) {
return list.stream()
.filter(type::isInstance)
.map(type::cast)
.collect(Collectors.toList());
}
在实际项目中,合理使用List和泛型可以显著提高代码的类型安全性和可维护性。我个人的经验是:对于查询操作多的场景优先选择ArrayList,修改操作多的考虑LinkedList;泛型使用时尽量明确边界,避免过度使用通配符导致代码可读性下降。