Java集合框架中的List接口是最常用的数据结构之一,它代表一个有序的元素序列。与数组不同,List的长度是可变的,这在实际开发中提供了极大的灵活性。ArrayList和LinkedList是List接口的两个经典实现类,它们各自有着不同的底层实现和适用场景。
ArrayList基于动态数组实现,内部使用Object[]数组存储元素。当添加元素导致容量不足时,会自动扩容为原来的1.5倍(JDK1.8)。这种实现使得ArrayList在随机访问时性能极佳(时间复杂度O(1)),但在中间位置插入或删除元素时需要移动后续所有元素(时间复杂度O(n))。因此,ArrayList特别适合读多写少的场景。
java复制// ArrayList扩容核心代码片段(java.util.ArrayList)
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);
}
LinkedList采用双向链表实现,每个节点(Node)包含前驱指针、后继指针和元素值。这种结构使得LinkedList在任意位置插入和删除元素都非常高效(时间复杂度O(1)),但随机访问需要从头或尾遍历链表(时间复杂度O(n))。LinkedList还实现了Deque接口,可以作为双端队列使用。
实际开发中选择ArrayList还是LinkedList,关键看操作类型比例。统计显示,80%以上的场景更适合ArrayList,因为现代CPU缓存对数组连续内存访问更友好。只有在频繁在列表中间增删元素时,LinkedList才有明显优势。
List接口提供了丰富的操作方法,但不同实现的性能特征差异显著:
| 操作 | ArrayList时间复杂度 | LinkedList时间复杂度 |
|---|---|---|
| get(int) | O(1) | O(n) |
| add(E) | 均摊O(1) | O(1) |
| add(int, E) | O(n) | O(1) |
| remove(int) | O(n) | O(1) |
| contains(Object) | O(n) | O(n) |
实际编码中常见的性能陷阱:
List的遍历有多种方式,但性能差异明显:
java复制// 方式1:普通for循环(适合ArrayList)
for(int i=0; i<list.size(); i++) {
Object obj = list.get(i);
}
// 方式2:增强for循环(底层使用迭代器)
for(Object obj : list) {
// ...
}
// 方式3:显式使用迭代器(适合LinkedList)
Iterator<Object> it = list.iterator();
while(it.hasNext()) {
Object obj = it.next();
}
实测表明:对于包含100万元素的LinkedList,方式1耗时超过3000ms,而方式3仅需约15ms。这是因为LinkedList的get(int)方法需要遍历链表,而迭代器会记住当前位置。
泛型的核心价值是在编译期进行类型检查,避免运行时的ClassCastException。没有泛型时,我们需要手动强制类型转换:
java复制List rawList = new ArrayList();
rawList.add("hello");
String str = (String) rawList.get(0); // 需要显式转换
使用泛型后,类型信息直接内嵌在代码中:
java复制List<String> genericList = new ArrayList<>();
genericList.add("hello");
String str = genericList.get(0); // 自动类型推断
泛型擦除是Java泛型的实现特点,编译器会在编译后移除类型参数信息(变为Object)。因此以下写法在运行时是等价的:
java复制// 编译前
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
// 编译后(类型擦除)
List list1 = new ArrayList();
List list2 = new ArrayList();
泛型通配符?提供了更灵活的类型关系表达:
List<? extends Number> 表示Number或其子类List<? super Integer> 表示Integer或其父类List<?> 表示未知类型PECS原则(Producer Extends, Consumer Super)是使用通配符的重要指南:
extendssuperjava复制// 生产者示例
public static double sum(List<? extends Number> list) {
double sum = 0;
for(Number num : list) {
sum += num.doubleValue();
}
return sum;
}
// 消费者示例
public static void addNumbers(List<? super Integer> list) {
for(int i=1; i<=10; i++) {
list.add(i);
}
}
标准List实现都不是线程安全的,多线程环境下需要考虑同步方案:
java复制List<String> syncList = Collections.synchronizedList(new ArrayList<>());
java复制List<String> cowList = new CopyOnWriteArrayList<>();
java复制List<String> list = new ArrayList<>();
// ...
synchronized(list) {
if(!list.contains(item)) {
list.add(item);
}
}
性能对比(100线程并发读写):
Java 9引入了List.of()工厂方法创建不可变集合:
java复制List<String> immutableList = List.of("a", "b", "c");
immutableList.add("d"); // 抛出UnsupportedOperationException
不可变集合的优势:
创建不可变集合的几种方式:
Collections.unmodifiableList()List.copyOf()List.of()ImmutableList这个异常通常发生在迭代过程中修改集合时:
java复制List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for(String s : list) {
if(s.equals("b")) {
list.remove(s); // 抛出异常
}
}
解决方案:
java复制Iterator<String> it = list.iterator();
while(it.hasNext()) {
if(it.next().equals("b")) {
it.remove(); // 安全删除
}
}
由于类型擦除,直接创建泛型数组是非法的:
java复制T[] arr = new T[10]; // 编译错误
正确解决方案:
java复制T[] arr = (T[]) new Object[10];
java复制T[] arr = (T[]) Array.newInstance(clazz, length);
java复制List<String> list = new ArrayList<>(1000); // 避免多次扩容
java复制// 低效
for(String item : items) {
list.add(item);
}
// 高效
list.addAll(items);
选择合适实现:
避免多余操作:
java复制// 不推荐
if(!list.contains(item)) {
list.add(item);
}
// 推荐(Set更合适)
boolean added = list.add(item);
List可以方便地转换为Stream进行函数式操作:
java复制List<String> filtered = list.stream()
.filter(s -> s.length() > 3)
.sorted()
.collect(Collectors.toList());
常用Stream操作:
filter():元素过滤map():元素转换sorted():排序distinct():去重collect():收集结果Java 9引入了更简洁的集合创建方式:
java复制List<String> list = List.of("a", "b", "c");
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("a", 1, "b", 2);
特点:
Java 16将Stream.toList()作为标准API:
java复制List<String> list = stream.toList(); // 替代collect(Collectors.toList())
新方法特点: