Collections.unmodifiableList并非真正不可变,它仅通过强引用代理原列表并拦截写操作,原列表修改会实时反映;真正不可变需切断原始引用或使用List.of()等隔离实现。

直接说结论:Collections.unmodifiableList 不创建新集合,只是给原 List 套一层“只读外壳”,原集合一旦被修改,不可变视图会立刻反映变化(甚至抛出 UnsupportedOperationException),它**不是真正意义上的不可变集合**。
为什么调用 unmodifiableList 后还能看到原列表的修改?
因为 unmodifiableList 返回的是一个内部静态类 UnmodifiableRandomAccessList(或 UnmodifiableList),它持有一个对原始 List 的**强引用**,所有读操作(如 get()、size())都委托给底层列表执行。它只拦截写操作(如 add()、set()),并统一抛出 UnsupportedOperationException。
这意味着:
- 如果原始列表是
new ArrayList(),且你在unmodifiableList创建后继续往里面add(),那么不可变视图的size()和get(i)会返回新内容 - 如果你把原始列表传给了其他代码并被意外修改,不可变视图将“被动同步”这些变更——这不是 bug,是设计使然
- 它不阻止并发修改,多线程下仍可能触发
ConcurrentModificationException
如何避免“假不可变”陷阱?
关键在于:**切断对原始可变列表的引用**。否则,只要还有人能拿到原列表,unmodifiableList 就形同虚设。
立即学习“Java免费学习笔记(深入)”;
推荐做法:
- 立即用新变量接收结果,并将原始引用置为
null(或作用域结束) - 更稳妥的方式是先复制再封装:
List
mutable = new ArrayList<>(Arrays.asList("a", "b")); List immutable = Collections.unmodifiableList(new ArrayList<>(mutable)); - 如果数据来自外部(比如方法参数),别直接包装,先做防御性拷贝:
public void process(List
input) { List safeCopy = new ArrayList<>(input); List readOnly = Collections.unmodifiableList(safeCopy); // 后续只使用 readOnly,丢弃 input 和 safeCopy 引用 }
Java 9+ 替代方案:用 List.of() 更安全
List.of()(以及 Set.of()、Map.of())才是真正的不可变集合实现:它们不持有外部引用、不允许 null、内部结构不可修改、且序列化友好。它的底层是私有不可变数组,没有“外壳代理”逻辑。
注意差异:
-
Collections.unmodifiableList允许null元素;List.of()不允许,遇到null直接抛NullPointerException -
List.of()在空集合或单元素时有专门优化,内存更紧凑 -
List.of()是Serializable且实现了equals/hashCode;而unmodifiableList的equals行为依赖底层列表实现 - 如果必须支持
null或需要动态构造(比如从流中收集),可用ImmutableList.copyOf(list)(Guava)或Lists.immutableListOf(...)
真正不可变的关键不在“是否报错”,而在“是否隔离源头”。哪怕用了 unmodifiableList,只要原始引用还活着,就不是终点。










