java 9+ 推荐用 set.of()、list.of()、map.of() 创建真正不可变集合,不接受 null,超10元素需用变长参数或 collectors;java 8 只能用 collections.unmodifiablexxx(),但仅为只读视图,存在透传修改风险。

Java 9+ 直接用 Set.of()、List.of()、Map.of() 创建不可变集合
这是最简单也最推荐的方式,适用于元素数量固定、无重复(对 Set)、键不重复(对 Map)的场景。生成的集合是真正不可变的:调用 add()、remove()、clear() 等任何修改方法都会抛出 UnsupportedOperationException。
注意点:
-
List.of()和Set.of()不接受null元素;Map.of()键和值都不能为null - 元素数量超过 10 个时,需改用
List.of(E...)的变长参数重载,或用Arrays.asList()+Collectors.toUnmodifiableList()(Java 10+) - 底层实现是高度优化的紧凑结构,内存占用比传统包装类小得多
Java 8 及更早版本只能靠 Collections.unmodifiableXXX() 包装
比如 Collections.unmodifiableList(new ArrayList(Arrays.asList("a", "b")))。但这种“不可变”只是表面的:它返回的是一个只读视图,原始被包装的集合如果被其他引用修改,变化会透传到该视图中。
常见误用陷阱:
立即学习“Java免费学习笔记(深入)”;
- 把
unmodifiableList当作安全副本传递,结果上游悄悄改了原ArrayList,下游逻辑出错 - 没意识到
unmodifiableMap返回的keySet()或values()仍是可变的(它们是原始 map 的视图) - 试图用
unmodifiableSet包装一个本身含重复元素的List,不会去重,也不报错,只是“假装”是 Set
需要深度不可变?得自己复制 + 封装,或用 Guava 的 ImmutableList
当集合元素本身也是可变对象(比如 List<stringbuilder></stringbuilder>),即使外层集合不可变,内部对象仍可能被修改。这时必须做深拷贝——而标准库不提供自动深拷贝支持。
可行方案:
- 手动遍历,对每个元素调用其不可变等价物(如把
StringBuilder转成String) - 使用 Guava 的
ImmutableList.copyOf(),它会在构造时做防御性拷贝(对实现了Cloneable或有明确不可变语义的类型) - 自定义不可变容器类,把构造器设为私有,所有字段用
final,且不暴露可变内部状态(例如不返回内部数组引用)
为什么 Stream.collect(Collectors.toUnmodifiableList()) 在 Java 10+ 才安全?
这个写法看起来很现代,但它依赖的是 Java 10 引入的 Collectors.toUnmodifiableList() —— 它内部调用的是 List.of()(Java 9+),不是包装器。如果在 Java 8 项目里误用了这个 collector,编译直接失败。
另外要注意:
- 它要求 stream 源是有限的;无限流会触发 OOM
- 收集过程中若发生并发修改(比如并行流 + 外部可变集合),行为未定义
- 和
List.of()一样,不接受null,遇到会抛NullPointerException
真正难的从来不是“怎么标成不可变”,而是确认整个数据链路——从创建、传递、到消费——没有意外的可变引用泄漏。这点在多线程或模块边界处最容易翻车。










