subList() 返回原始列表的视图而非新列表,修改会双向同步;需独立副本时须显式构造 new ArrayList(list.subList(from, to)),且注意不同实现类(如 ArrayList 与 LinkedList)对结构性操作的支持差异。

subList() 返回的是原始列表的视图,不是新列表
调用 List.subList(fromIndex, toIndex) 得到的不是一份独立拷贝,而是一个「活的引用视图」:对子列表的修改会直接反映在原列表上,反之亦然。这是最常被误用的点——以为切片是安全的只读副本,结果改着改着把原数据搞乱了。
常见错误现象:ConcurrentModificationException(尤其在迭代子列表时原列表被其他线程或代码段修改)、UnsupportedOperationException(对不可变列表调用 subList() 后尝试 add() 或 clear())。
- 如果需要独立副本,必须显式构造:
new ArrayList(list.subList(1, 4)) -
fromIndex必须 ≥ 0,toIndex必须 ≤ 原列表 size,且fromIndex ≤ toIndex,否则抛IndexOutOfBoundsException - 子列表不支持
add()、remove()、retainAll()等结构性修改操作(除非底层实现明确支持,如ArrayList允许),但set()和get()总是可用的
ArrayList.subList() 支持部分修改操作,但 LinkedList 不行
ArrayList 的 subList() 实现允许在子列表上执行 set()、remove()、add()(插入到子列表末尾或中间),这些操作会同步更新原 ArrayList;而 LinkedList 的 subList() 在 JDK 8+ 中仍返回一个不支持结构性修改的视图,调用 add() 或 remove() 会直接抛 UnsupportedOperationException。
- 不要假设所有
List实现的subList()行为一致;生产环境若需稳定修改能力,优先用ArrayList+ 显式拷贝 -
subList().clear()对ArrayList有效(清空原列表对应区间),但对LinkedList无效 - 性能影响:视图本身创建开销极小(O(1)),但后续每次
get()都带一次边界检查和索引偏移计算,比直接访问原列表略慢
迭代子列表时修改原列表会触发 ConcurrentModificationException
即使单线程,只要在遍历 subList() 的过程中,通过原列表引用(或子列表自身)修改了结构(比如 add()、remove()),就会触发 ConcurrentModificationException —— 因为子列表和原列表共享同一个 modCount。
- 安全做法:先收集要修改的位置/值,遍历结束后统一操作;或改用传统 for 循环配合索引(避开迭代器检查)
- 避免写法:
for (String s : list.subList(0, 3)) { list.add(s); }→ 必炸 - 注意:仅读取(
get())或替换(set())不会改变modCount,所以是安全的
序列化 subList() 视图会导致不可预期行为
subList() 返回的对象本身通常不实现 Serializable(JDK 中 ArrayList.SubList 是包私有类,未实现该接口),强行序列化会抛 NotSerializableException;即使某些版本能过,反序列化后也无法保证与原列表状态一致。
- 需要持久化切片数据?必须转成新集合:
new ArrayList(list.subList(a, b)) - 不要把
subList()结果存入缓存、RPC 参数或数据库实体字段中 - 单元测试里容易漏掉这个点:本地跑通,上线后遇到序列化场景就失败
subList() 结果时,调用方根本不知道自己拿到的是个随时会联动变更的活链接。

















