# 20. 为什么 subList 不是独立的 List,而是原 List 的视图?

# 标准答案

subList(int fromIndex, int toIndex) 返回的不是新的 List 对象,而是原 List 的一个视图(View)。这意味着 subList 依赖原 List,任何修改 subList 都会影响原 List,反之亦然。

设计原因:

  1. 避免数据复制,提高性能——如果 subList 复制数据,创建新 List,将导致额外的时间和空间开销
  2. 快速分页、切片——subList 只维护索引偏移量O(1) 时间获取子列表,适用于大数据量操作
  3. 统一管理数据——修改 subList 直接影响原 List,确保数据一致性。

注意事项:

  • subList 不是独立 List,不要长期持有,可能导致内存泄漏
  • 删除 subList 元素后,访问原 List 可能抛 ConcurrentModificationException
  • 如果需要真正的副本,必须 new ArrayList<>(subList)

# 答案解析

# 1. subList 的底层原理

AbstractListArrayList 的父类)中,subList() 直接创建 SubList 视图,其核心逻辑:

  • 不复制数据,仅存储List 引用、起始索引、长度
  • 所有操作都映射到原 List,如 get()set()remove()
  • modCount 监测修改,防止并发修改异常

核心源码(JDK 18 ArrayList#subList()):

public List<E> subList(int fromIndex, int toIndex) {
    rangeCheck(fromIndex, toIndex, size); // 检查索引合法性
    return new SubList(this, fromIndex, toIndex);
}
1
2
3
4

SubList 结构:

private static class SubList<E> extends AbstractList<E> {
    private final AbstractList<E> parent; // 直接引用原 List
    private final int offset; // 起始索引
    private int size; // 子列表大小

    public E get(int index) {
        return parent.get(index + offset); // 访问原 List 数据
    }

    public void set(int index, E element) {
        parent.set(index + offset, element); // 修改原 List 数据
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

示例:

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 4); // [2, 3, 4]
subList.set(0, 99); 
System.out.println(list); // [1, 99, 3, 4, 5]
1
2
3
4

subList 修改,list 也发生变化,因为它们共享底层数组

# 2. 为什么 subList 不能是独立的 List?

# (1)避免数据拷贝,提高性能

如果 subList() 创建新 List 并复制数据

public List<E> subList(int fromIndex, int toIndex) {
    return new ArrayList<>(super.subList(fromIndex, toIndex));
}
1
2
3
  • 缺点:会导致额外的内存占用 + O(n) 复制开销
  • 影响:如果原 List100W+ 数据,每次 subList() 都会复制一部分,性能下降

所以,Java 选择返回视图,避免不必要的内存开销和时间损耗。

# (2)支持大规模数据操作

在大数据场景(如分页查询),如果 subList 复制数据,每次查询都要遍历并复制大数组,会严重影响性能
返回视图可以让 subList() 在 O(1) 时间内获取子列表,即使数据量巨大,也能快速切片。

# (3)子列表和原列表的同步性

如果 subList() 生成独立 List,修改 subList 不会影响原 List,导致数据不一致:

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> copyList = new ArrayList<>(list.subList(1, 4));
copyList.set(0, 99); 
System.out.println(list); // [1, 2, 3, 4, 5] (没有变化)
1
2
3
4

subList 是视图,确保修改同步:

List<Integer> subList = list.subList(1, 4);
subList.set(0, 99);
System.out.println(list); // [1, 99, 3, 4, 5] (修改生效)
1
2
3

# 3. subList 的常见坑

# (1)删除 subList 影响原 List
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 4);
subList.clear(); 
System.out.println(list); // [1, 5]
1
2
3
4

subList.clear() 会删除原 List 的数据

# (2)subList 后原 List 不能修改
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 4);
list.remove(2); // 删除 3
System.out.println(subList); // ConcurrentModificationException!
1
2
3
4

subList 依赖原 ListmodCount,修改后检测到 modCount 变化,直接抛异常

# (3)避免 subList 长期持有,防止内存泄漏

subList() 持有原 List引用,如果 subList 在长期任务中持有,但原 List 没有释放,会导致内存泄漏

# 4. subList 和其他方式的对比

方式 是否拷贝数据 线程安全 适用场景
subList() ❌ 仅创建视图 ❌ 受原 List 影响 大数据分页、高效拆分
stream().skip().limit() ✅ 生成新流 流式计算,避免共享
for + addAll() ✅ 复制数据 独立副本,避免影响原数据

# 深入追问

  • subListLinkedList 中性能如何?
  • 如何在 CopyOnWriteArrayList 中安全使用 subList()
  • subList() 为什么会引发 ConcurrentModificationException

# 相关面试题

  • subList() 为什么比 for 循环切片更高效?
  • 如何在分页查询中正确使用 subList()
  • 如何保证 subList() 操作不会影响原 List 的完整性?