SubList是原列表的活切片,写操作直接影响原列表,存在双向修改干扰、结构性变更异常和内存泄漏三类风险。

SubList 不是副本,而是原列表的“活切片”。你对它的任何写操作(set、add、remove、clear)都会直接作用到原列表底层数据上——这不是 bug,是设计使然。但正因如此,它带来三类典型风险:双向修改干扰、结构性变更引发异常、以及隐性内存泄漏。
修改子列表会实时改变原列表
SubList 的 set(i, e) 实际执行的是 parent.set(offset + i, e);add(i, e) 本质是往原列表指定位置插入;clear() 等价于删掉原列表对应范围的元素。例如:
- 原列表为
[1,2,3,4,5],sub = list.subList(1,4)→ 视图为[2,3,4] -
sub.set(0, 9)→ 原列表变成[1,9,3,4,5] -
sub.add(1, 8)→ 原列表变成[1,9,8,3,4,5],且sub变为[9,8,3,4]
遍历或操作子列表时修改原列表结构会抛 ConcurrentModificationException
SubList 和原列表共享同一个 modCount(修改计数器)。只要在迭代子列表过程中,原列表调用了 add()、remove()、clear() 等结构性方法,下一次访问子列表(比如 next() 或 get())就会触发校验失败。
- 常见场景:用
for-each遍历subList,循环体内却调用list.remove(...) - 陷阱延伸:
subList.stream().forEach(...)或subList.forEach(...)同样基于其迭代器,一样会崩 - 安全做法:需边遍历边改原列表时,先复制:
new ArrayList(subList),再操作副本
长期持有 subList 可能导致大对象无法回收
SubList 内部持有一个指向原 ArrayList 实例的强引用。哪怕你只取了前 5 个元素,只要这个 SubList 对象还被变量、缓存、静态字段引用着,整个原始百万级列表就一直留在堆里。
- 典型 OOM 场景:从大数据集解析后反复调用
subList(0,10)存入全局缓存 - 排查线索:堆转储中看到小 size 的 List 对象却占着巨大
elementData数组 - 缓解方式:JDK 9+ 可用
ImmutableList.copyOf(subList)切断引用(但结果不可变);最通用仍是显式构造新 ArrayList
序列化与跨进程传递基本不可行
SubList 类型(如 ArrayList.SubList)未正确实现 Serializable,或其实现依赖原列表运行时状态。尝试序列化它通常直接抛 NotSerializableException;即使绕过,反序列化后也无法还原视图逻辑。
- 高频出错点:Dubbo 接口返回类型误写为
List.subList(...),Feign 调用失败 - 根本解法:对外暴露数据前,统一转成
new ArrayList(list.subList(a,b))或使用Arrays.asList(...)构造轻量新列表










