subList返回原List的动态视图而非副本,修改子列表会直接影响原列表,且可能抛ConcurrentModificationException或IndexOutOfBoundsException;需手动复制才能获得安全快照。

subList 返回的是原 List 的视图,不是新副本
调用 subList 不会复制元素,而是返回一个「代理视图」——底层仍指向原始 ArrayList(或 LinkedList)的同一块数据。这意味着:修改子列表会直接影响原列表,反之亦然。
- 对
subList调用add()、remove()、set()会抛UnsupportedOperationException(除非原列表本身支持结构修改且是ArrayList) -
subList的size()变化时,原列表的size()也会变(例如在ArrayList上对subList调用clear(),会真实删掉原列表对应段) - 如果原列表后续被外部修改(比如另一个线程调用了
add()),再访问subList可能触发ConcurrentModificationException
越界判断只在创建时做,不保护后续操作
subList(fromIndex, toIndex) 在调用瞬间检查 fromIndex 和 toIndex 是否合法(0 ≤ from ≤ to ≤ size),但不会为返回的子列表「绑定」当时的长度。一旦原列表被增删,子列表的逻辑边界就可能失效。
- 常见错误:先
list.subList(0, 5),再list.clear(),接着调用子列表的get(0)→ 抛IndexOutOfBoundsException(因为子列表内部仍记着「应有 5 个元素」,但底层数组已空) - 更隐蔽的情况:多线程下,A 线程拿到
subList,B 线程在 A 使用前修改了原列表,A 再遍历时直接崩溃 - 注意:
toIndex是「开区间」,subList(0, list.size())合法,但subList(0, list.size() + 1)必炸
想安全截取?得手动复制,别依赖 subList
如果目标是「拿一段快照」,而不是「动态联动视图」,subList 就不是正确工具。必须显式创建新容器。
- 最通用:用构造器
new ArrayList(list.subList(from, to))—— 先视图取范围,再复制进新对象 - Java 10+ 可用
List.copyOf(list.subList(from, to)),但注意它返回不可变列表,后续不能改 - 若原列表是
LinkedList,subList性能更差(O(n) 随机访问),此时直接遍历 +add反而更稳 - 避免写成
list.subList(from, to).stream().collect(Collectors.toList())—— 多余包装,无必要开销
subList 的迭代器没有 fail-fast 保障
虽然 ArrayList 的迭代器默认是 fail-fast 的,但其 subList 返回的迭代器在某些 JDK 版本中(如 Java 8)对「原列表结构性修改」的检测并不严格——可能不报错,而是行为未定义(跳过元素、重复遍历、甚至无限循环)。
立即学习“Java免费学习笔记(深入)”;
- 不要假设
for (String s : list.subList(0, 3)) { ... }在原列表被并发修改时还能安全执行 - 如果必须边遍历子列表边改原列表,先转成数组:
String[] arr = list.subList(0, 3).toArray(new String[0]),再遍历arr - 该问题在 Java 17+ 有所收敛,但仍不建议依赖——语义上,视图不该承担强一致性责任
真正麻烦的从来不是「怎么截」,而是「截完谁还在引用原列表」。只要有一个地方还拿着 subList 结果,原列表就不能随意增删;而一旦忘了它是视图,调试时看到数据莫名消失或越界,就得回头翻源码确认边界快照是否还有效。










