1. Java List 接口概述
List 是 Java 集合框架中最常用的接口之一,它代表一个有序的集合(也称为序列)。与 Set 不同,List 允许重复元素,并且通过索引精确控制每个元素的插入位置。List 接口扩展了 Collection 接口,添加了大量面向位置的操作,以及获取列表迭代器的方法。
在实际开发中,我们最常使用的是 ArrayList 和 LinkedList 这两种实现。但 Java 标准库中其实提供了更多 List 实现,每种实现都有其特定的设计目的和使用场景。理解这些实现的内部机制和性能特点,对于编写高效、健壮的 Java 代码至关重要。
提示:List 接口位于 java.util 包中,自 Java 1.2 引入,是 Java 集合框架的基石之一。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心实现类详解
2.1 ArrayList - 动态数组实现
ArrayList 是 List 接口最常用的实现,它基于动态数组的概念。所谓动态数组,是指能够根据需要自动增长和缩小的数组。与普通数组不同,ArrayList 的容量会自动调整,开发者无需手动处理扩容逻辑。
java复制// 典型 ArrayList 使用示例
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add(1, "Orange"); // 在索引1处插入
ArrayList 内部使用一个 Object 数组来存储元素。当添加元素导致容量不足时,ArrayList 会自动创建一个更大的新数组(通常是原容量的1.5倍),并将所有元素复制到新数组中。这个扩容过程对开发者是透明的,但需要注意它会导致额外的性能开销。
性能特点:
- 随机访问:O(1) - 通过索引直接访问数组元素
- 插入/删除:O(n) - 需要移动后续元素
- 尾部插入:平均O(1) - 不考虑扩容的情况下
扩容机制详解:
ArrayList 默认初始容量为10。当添加第11个元素时,会触发扩容。扩容时,新容量计算方式为:
java复制int newCapacity = oldCapacity + (oldCapacity >> 1); // 相当于1.5倍
如果明确知道最终会存储多少元素,最好在创建 ArrayList 时就指定初始容量,避免多次扩容:
java复制List<Integer> numbers = new ArrayList<>(1000); // 初始容量1000
2.2 LinkedList - 双向链表实现
LinkedList 采用双向链表数据结构实现 List 接口。每个元素(节点)都保存了对前驱和后继节点的引用,这使得在列表任意位置插入和删除元素都非常高效。
java复制// LinkedList 基本操作
LinkedList<String> names = new LinkedList<>();
names.add("Alice");
names.addFirst("Bob"); // 添加到头部
names.addLast("Charlie"); // 添加到尾部
String first = names.removeFirst(); // 移除并返回第一个元素
LinkedList 不仅实现了 List 接口,还实现了 Deque 接口,因此可以用作栈、队列或双端队列:
java复制// 作为栈使用
LinkedList<Integer> stack = new LinkedList<>();
stack.push(1); // 压栈
stack.push(2);
int top = stack.pop(); // 弹栈,返回2
// 作为队列使用
LinkedList<String> queue = new LinkedList<>();
queue.offer("A"); // 入队
queue.offer("B");
String head = queue.poll(); // 出队,返回"A"
性能特点:
- 随机访问:O(n) - 需要从头或尾开始遍历
- 插入/删除:O(1) - 已知位置时只需调整指针
- 头部/尾部操作:O(1) - 特别高效
内存占用分析:
LinkedList 的每个元素都需要额外的内存来存储前后节点的引用(每个引用在32位JVM中占4字节,64位JVM中占8字节)。因此,对于小型集合,LinkedList 的内存开销可能比 ArrayList 更大。
2.3 Vector - 线程安全的动态数组
Vector 是 Java 早期版本提供的线程安全动态数组实现。它的功能与 ArrayList 类似,但所有方法都通过 synchronized 关键字实现了同步,保证了线程安全。
java复制Vector<String> vec = new Vector<>();
vec.add("Element1");
vec.addElement("Element2"); // 与add()等效的旧方法
String first = vec.firstElement(); // 获取第一个元素
虽然 Vector 是线程安全的,但由于同步带来的性能开销,以及更现代的并发集合类的出现,Vector 已经不再推荐在新代码中使用。替代方案包括:
- Collections.synchronizedList() 包装的 ArrayList
- CopyOnWriteArrayList(适用于读多写少的场景)
历史背景:
Vector 自 Java 1.0 就已存在,早于集合框架(Java 1.2引入)。为了保持向后兼容,它被保留了下来,但已被标记为"legacy"(遗留)类。
2.4 Stack - LIFO 栈实现
Stack 类继承自 Vector,实现了后进先出(LIFO)的栈结构。它提供了标准的栈操作:push(压栈)、pop(弹栈)和 peek(查看栈顶)。
java复制Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
int top = stack.peek(); // 2,不移除
int popped = stack.pop(); // 2,移除
虽然 Stack 仍然可用,但官方文档建议使用更现代的 Deque 实现(如 ArrayDeque)来替代 Stack:
java复制Deque<Integer> betterStack = new ArrayDeque<>();
betterStack.push(1);
betterStack.push(2);
int item = betterStack.pop();
不推荐使用 Stack 的原因:
- 继承自 Vector,暴露了不必要的方法(如ins
