collections.unmodifiablelist等方法仅提供只读外壳,不阻止底层集合修改;真隔离需先复制(如new arraylist(original)),java 9+的list.of()更安全但限制多;unmodifiableset不自动去重,嵌套集合需手动逐层冻结。

unmodifiableList 等方法不阻止原集合被修改
它只是返回一个“只读外壳”,底层仍是原 ArrayList 或 HashMap。只要有人还握着原始引用,调用 add()、clear() 甚至 set(0, x),不可变视图里立刻能看到变化——这不是 bug,是设计使然。
常见错误现象:UnsupportedOperationException 只在你试图通过不可变视图修改时抛出,但没人拦着你改底层数组。
- 如果真要隔离数据,得先复制:用
new ArrayList(original)再套Collections.unmodifiableList() - 对
Map同理,new HashMap(originalMap)是必须步骤,否则unmodifiableMap()形同虚设 - 注意深浅拷贝:这层复制只到第一层,若 value 是可变对象(比如
ArrayList),仍可能被外部修改
Java 9+ 的 List.of() / Map.of() 更安全但有硬限制
它们创建的是真正不可变实例,不共享内部数组,也不允许 null 元素或重复 key。但代价明显:
-
List.of()不接受null—— 传入null直接抛NullPointerException -
Map.of()最多支持 10 个键值对;超限时得用Map.ofEntries()+Map.entry() - 所有元素在构造时就被冻结,连反射都绕不过去(内部用私有 final 数组实现)
性能上,这些工厂方法比 unmodifiableXXX 略快(少一层代理),但灵活性差很多——比如无法从已有集合构造,也不能动态扩容。
立即学习“Java免费学习笔记(深入)”;
unmodifiableSet 对重复元素无校验,可能掩盖逻辑问题
如果你传一个含重复元素的 ArrayList 给 Collections.unmodifiableSet(),它不会去重,也不会报错,而是直接包装成一个行为异常的 Set 视图:调用 contains() 可能返回 true,但 size() 却大于实际唯一元素数。
- 正确做法:先用
new HashSet(list)去重,再用Collections.unmodifiableSet() - 或者一步到位:用
Set.copyOf(list)(Java 10+),它会自动去重并返回不可变实例 - 别依赖
unmodifiableSet()来“修复”脏数据——它不是转换工具,只是防护罩
嵌套集合的不可变性需要手动穿透处理
一个 unmodifiableList 包含几个 ArrayList,那这些子列表本身还是可变的。外部代码拿到某个子项后调用 subList.add(x),父列表内容就变了。
- 没有自动递归冻结机制,Java 标准库不提供“深度不可变”工具
- 如需真正隔离,得逐层复制:对每个子集合也做
new ArrayList(sub),再包一层unmodifiableList() - Lombok 的
@Value或 Guava 的ImmutableList.copyOf()能简化部分场景,但依然不解决嵌套可变性
最易被忽略的一点:不可变性不是传染的,它只作用于你明确包装的那一层。想靠一个 unmodifiableList 拦住所有修改,基本等于没拦。










