List

ℹ️定位说明

本篇是 List 系列的总览:先建立"List 是什么、三实现怎么选"的整体认知,再进入各实现类详篇。动线:本篇 → ArrayListLinkedListVector

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 接口有多个实现类:ArrayListLinkedListVector

维度 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 倍增长
内存开销 较小(数组紧凑存储,无额外指针) 较大(每个节点需存储 prevnext 指针) 较大(同步机制额外开销 + 数组存储)
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 的勾连

containsremove(Object)indexOf 判断"是不是同一个元素"靠的是 equals(),而 HashSet/HashMap 场景还依赖 hashCode()——自定义类放进集合前不重写这两个方法,就会出现"明明放进去却 contains 不到"的问题。详见 Object 通用方法


⬅️ 00-容器总览 🏠 00-Java ➡️ ArrayList