List
定位说明
本篇是 List 系列的总览:先建立"List 是什么、三实现怎么选"的整体认知,再进入各实现类详篇。动线:本篇 → ArrayList → LinkedList → Vector。
Java 中的 List 接口:有序、可重复的集合框架核心
List 是 Java 集合框架中最常用的接口之一,继承自Collection,具有以下核心特性:
- 有序性:元素按插入顺序存储,支持通过索引(下标)访问元素。
- 可重复性:允许存储重复元素。
- 丰富的操作接口:提供
get(int index)、add(int index, E element)、remove(int index)等基于索引的操作方法,方便数据的随机访问和位置调整。
继承关系
graph TD
Iterable["Iterable<E>"] --> Collection["Collection<E>"]
Collection --> List["List<E>"]
List --> ArrayList["ArrayList<br/>动态数组"]
List --> LinkedList["LinkedList<br/>双向链表"]
List --> Vector["Vector<br/>同步动态数组(遗留)"]
Vector --> Stack["Stack<br/>栈(遗留)"]
一句话记忆:List = 可按下标操作的动态序列。ArrayList 是默认选择,LinkedList 换来了头尾操作的高效,Vector 是历史遗留(见其篇内"为什么现在不用 Vector")。
List 接口有多个实现类:ArrayList、LinkedList、Vector
| 维度 | ArrayList | LinkedList | Vector |
|---|---|---|---|
| 底层结构 | 动态数组(Object[],连续内存) |
双向链表(Node节点,非连续内存) |
动态数组(Object[],连续内存) |
| 线程安全 | ❌ 非线程安全 | ❌ 非线程安全 | ✅ 线程安全(方法加 synchronized) |
| 随机访问效率 | ✅ 快速(O (1),索引直接定位) | ❌ 低效(O (n),需遍历链表) | ✅ 快速(O (1),索引直接定位) |
| 插入 / 删除效率 | ❌ 尾部 O (1)(若需扩容则 O (n)) 中间 O (n)(移动元素) | ✅ 头尾 O (1)(改指针) 中间 O (n)(遍历定位) | ❌ 尾部 O (1)(若需扩容则 O (n)) 中间 O (n)(移动元素) |
| 扩容机制 | 自动扩容(默认初始 10,1.5 倍增长) | 无需扩容(链表天然动态) | 自动扩容(默认初始 10,2 倍增长) |
| 内存开销 | 较小(数组紧凑存储,无额外指针) | 较大(每个节点需存储 prev 和 next 指针) |
较大(同步机制额外开销 + 数组存储) |
| Null 元素支持 | ✅ 可存储多个 null |
✅ 可存储多个 null |
✅ 可存储多个 null |
| 适用场景 | 单线程、读多写少、随机访问频繁 (如缓存、索引表、数据库结果集) | 单线程、写多读少、频繁头尾操作 (如消息队列、栈 / 队列、链式数据结构) | 多线程、需要线程安全的随机访问场景 (如共享数据读写、旧系统兼容) |
| 典型应用 | for 循环遍历、排行榜(需索引) |
Deque 双端队列、LRU 缓存(头尾操作) |
多线程计数器、共享日志列表 |
| 替代方案 | 多线程时用 Collections.synchronizedList() 或 CopyOnWriteArrayList |
多线程时用 ConcurrentLinkedQueue |
优先用 ArrayList + 同步包装 或 CopyOnWriteArrayList |
常用方法速查
List<String> list = new ArrayList<>();
list.add("b"); // 尾部追加
list.add(0, "a"); // 指定位置插入(后面的元素整体后移)
list.set(0, "A"); // 覆盖指定位置元素,返回旧值
list.get(0); // 按下标取值
list.remove(0); // 按下标删(注意重载歧义,见下)
list.remove("A"); // 按对象删(equals 判等)
list.indexOf("A"); // 第一次出现的下标,没有返回 -1
list.contains("A"); // 是否包含(底层 indexOf)
list.size(); // 元素个数
list.isEmpty(); // 是否为空
list.sort(Comparator.comparingInt(String::length)); // 就地排序
list.subList(0, 2); // 截取 [0,2) 视图——改视图会影响原 List
list.clear(); // 清空
`remove` 的重载歧义(高频面试坑)
List<Integer> list 里调 remove(1) 删的是下标 1,不是元素 1——因为 remove(int index) 精确匹配优先于 remove(Object o)。要按元素删必须写 list.remove(Integer.valueOf(1))。
遍历的四种方式
List<String> list = List.of("a", "b", "c");
// 1. 普通for——需要下标时用
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 2. 增强for——只读遍历的首选
for (String s : list) {
System.out.println(s);
}
// 3. Iterator——遍历中要删元素时唯一安全的方式(fail-fast 见 ArrayList 篇)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("a".equals(s)) it.remove(); // 用迭代器自己的 remove,不是 list.remove
}
// 4. ListIterator——List 独有,可双向遍历、可修改
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
String s = lit.next();
lit.set(s.toUpperCase()); // 边遍历边改
}
while (lit.hasPrevious()) { // 此时光标在末尾,可反向再走一遍
System.out.println(lit.previous());
}
创建 List 的三种姿势(面试高频)
// 1. new —— 可变,日常主力
List<String> a = new ArrayList<>();
a.add("x"); // ✅ 可以增删改
// 2. Arrays.asList —— 定长视图
List<String> b = Arrays.asList("x", "y");
// b.add("z"); // ❌ 抛 UnsupportedOperationException
// 但 b.set(0, "z") 可以!—— 它是"长度固定的 List",不是不可变
// 3. List.of —— 真不可变(JDK 9+)
List<String> c = List.of("x", "y");
// c.add("z"); c.set(0, "z"); // ❌ 全部抛 UnsupportedOperationException
三者本质区别:能不能 add/remove 和能不能 set 是两件事——Arrays.asList 定长但可改元素,List.of 连元素都不能改。另外 List.of 不允许 null 元素,前两者允许。
与 equals/hashCode 的勾连
contains、remove(Object)、indexOf 判断"是不是同一个元素"靠的是 equals(),而 HashSet/HashMap 场景还依赖 hashCode()——自定义类放进集合前不重写这两个方法,就会出现"明明放进去却 contains 不到"的问题。详见 Object 通用方法。
💬 评论