List.subList() 返回值为何不能直接转成 ArrayList?

摘要:先说结论 很多人用 subList() 的时候,可能会想当然地认为它返回的是一个新的 ArrayList。但实际上,subList() 返回的是原 List 的一个视图(View),并不是一个独立的 ArrayList 对象。这样会
先说结论 很多人用 subList() 的时候,可能会想当然地认为它返回的是一个新的 ArrayList。但实际上,subList() 返回的是原 List 的一个视图(View),并不是一个独立的 ArrayList 对象。 // 这样会报 ClassCastException ArrayList<String> subArray = (ArrayList<String>) list.subList(0, 3); // 错误! 源码分析:subList 到底返回了什么? 我们先来看看 ArrayList 的 subList 方法源码: public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); } 可以看到,它返回的是一个 SubList 对象,这是 ArrayList 的私有内部类: private class SubList extends AbstractList<E> implements RandomAccess { private final AbstractList<E> parent; private final int parentOffset; private final int offset; int size; SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) { this.parent = parent; this.parentOffset = fromIndex; this.offset = offset + fromIndex; this.size = toIndex - fromIndex; this.modCount = ArrayList.this.modCount; } public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); } public E set(int index, E e) { rangeCheck(index); checkForComodification(); E oldValue = ArrayList.this.elementData(offset + index); ArrayList.this.elementData[offset + index] = e; return oldValue; } } 关键点: SubList 是 ArrayList 的私有内部类,继承 AbstractList,和 ArrayList 没有继承关系 它持有原 List 的引用(parent 字段) 它只记录了起始位置和长度(offset 和 size),没有复制数据 get/set 方法直接操作原 ArrayList 的底层数组 elementData 保存了创建时的 modCount,用于检测并发修改 所以 SubList 本质上就是一个视图,操作的是同一块内存区域。 基本用法 subList 方法签名:subList(fromIndex, toIndex),注意是左闭右开区间。 ArrayList<String> list = new ArrayList<>(); list.add("我"); list.add("爱"); list.add("J"); list.add("A"); list.add("V"); list.add("A"); List<String> newList = list.subList(2, 6); System.out.println("newList: " + newList); // [J, A, V, A] System.out.println("list: " + list); // [我, 爱, J, A, V, A] 到这里看起来挺正常,但因为 newList 只是原 list 的视图,它们之间的操作会相互影响。根据修改方式的不同,分为两种情况: 非结构化修改 修改 subList,原 List 也会变: List<String> newList = list.subList(2, 6); newList.set(0, "A"); // 将 J 改为 A System.out.println("newList: " + newList); // [A, A, V, A] System.out.println("list: " + list); // [我, 爱, A, A, V, A] 修改原 List,subList 也会变: list.set(2, "O"); System.out.println("更新后的 list: " + list); // [我, 爱, O, A, V, A] System.out.println("更新后的 newList: " + newList); // [O, A, V, A] 这就是视图的特性——它们指向同一块内存区域,结合前面的源码可以看到,get/set 方法操作的都是 ArrayList.this.elementData。 结构化修改(重点!) 修改 subList 没问题: List<String> newList = list.subList(2, 6); newList.add(1, "O"); // 在 newList 中插入元素 System.out.println("更新后的 list: " + list); // [我, 爱, J, O, A, V, A] System.out.println("更新后的 newList: " + newList); // [J, O, A, V, A] 两个 List 都正常更新了,因为 SubList 的 add 最终调用的是原 ArrayList 的 add,并且会同步更新 modCount。 修改原 List 会抛异常: List<String> newList = list.subList(2, 6); list.add(1, "超"); // 修改原 list 的结构 System.out.println("更新后的 list: " + list); // [我, 超, 爱, J, A, V, A] System.out.println("更新后的 newList: " + newList); // 💥 ConcurrentModificationException! 直接抛异常了! 为什么会抛异常? 来看 SubList 的校验代码: private void checkForComodification() { if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); } 原理是这样的: SubList 创建时保存了当时的 modCount 原 ArrayList 每次结构化修改都会 modCount++ SubList 的每个操作都会先调用 checkForComodification() 检查 如果发现原 ArrayList 的 modCount 和自己保存的不一致,直接抛异常 这就是 fail-fast(快速失败) 机制,用来尽早发现并发修改问题。 实际开发中怎么用? 1. 需要独立的 List List<String> newList = new ArrayList<>(list.subList(2, 6)); 用 ArrayList 的构造方法包一层,得到一个全新的独立 List,互不影响。 2. 利用视图特性 有时候就是想利用视图特性来修改原 List 的某一部分: 通过 subList 修改 ✅ 持有 subList 时对原 List 做结构化修改 ❌ 3. 一次性操作 java list.subList(2, 6).clear(); // 清空原 list 的某个区间 用完即扔,不会有并发修改问题,而且效率很高。