collections.unmodifiablelist仅提供运行时写操作拦截,不隔离底层引用,原始列表被修改时只读视图同步变化;真正不可变需用list.copyof或immutablelist.of等构建时固化方案。

为什么 Collections.unmodifiableList 不能真正防住数据篡改
它只挡得住「直接调用 list 接口方法」的修改,比如 add、set、clear,一旦底层原始 List 被其他地方改动,只读视图立刻同步暴露变化。
- 常见错误现象:
UnsupportedOperationException确实抛了,但有人误以为“数据安全了”,结果发现原始ArrayList还在别处被add了,只读列表里突然多出元素 - 根本原因:它返回的是原列表的「包装器」(wrapper),不是副本;内部仍持有着对原
List的强引用 - 使用场景:适合临时封禁某次方法返回值的写操作,比如 API 返回一个配置项列表,你不想调用方顺手
remove(0),但不适用于长期持有或跨模块共享
Collections.unmodifiableList 和 Arrays.asList 套用时的坑
很多人这么写:Collections.unmodifiableList(Arrays.asList("a", "b")),看起来很安全——其实底层是可变数组,而且 Arrays.asList 返回的 List 本身就不支持 add/remove,但支持 set,而 unmodifiableList 会把它也拦住。问题在于:如果原始数组被外部改了,比如你把返回的数组又传给了别人并被 array[0] = "x",那只读列表内容就变了。
- 参数差异:
Arrays.asList返回的是java.util.Arrays$ArrayList,不是java.util.ArrayList;它直接封装数组,没有复制 - 性能影响:零拷贝,轻量,但代价是无法隔离底层状态
- 正确做法:若需真正隔离,应先复制再包装,例如:
Collections.unmodifiableList(new ArrayList(originalList))
替代方案:什么时候该用 ImmutableList.of 或 List.copyOf
Java 9+ 的 List.copyOf 和 Guava 的 ImmutableList.of 才是真不可变——它们创建的是新对象,且内部实现禁止任何修改路径,连反射绕过都更难(Guava 还做了防御性检查)。
- 兼容性注意:
List.copyOf要求入参非null,且会做一次快照复制;传入的List如果本身还在被并发修改,复制过程可能抛ConcurrentModificationException - 使用场景:
List.copyOf适合已确定稳定的数据源;ImmutableList.of更适合小规模常量构造,比如ImmutableList.of("ERROR", "WARN", "INFO") - 关键区别:
Collections.unmodifiableList是运行时防护,靠异常拦截;后两者是构建时固化,从根源上没留修改入口
嵌套集合里只用 unmodifiableList 是无效的
如果你有 List<list>></list>,只对外层调用 unmodifiableList,里面每个子 List 依然能被 get(i).add("x") 修改——因为防护没递归下去。
- 常见错误现象:外层报
UnsupportedOperationException,但子列表内容悄悄变了,日志看不出异常 - 解决思路:要么逐层包装(手动或用工具类),要么换用深度不可变结构,如 Guava 的
ImmutableList<immutablelist>></immutablelist> - 性能提醒:逐层包装不复制内容,但嵌套越深,运行时检查开销略增;深度不可变结构初始化成本高,适合读多写绝无的场景
真正安全不是加一层 wrapper,而是控制谁持有可变引用、何时切断连接。很多 bug 出在“我以为别人不会改”,而不是“我有没有调用 unmodifiableList”。










