不可变集合是内容与结构均不可变、线程安全且无隐藏可变状态的集合;Java 9+ 的 List.of() 等工厂方法和 Guava 的 ImmutableList 才是真正不可变,而 Collections.unmodifiableXXX() 仅为只读视图。

什么是不可变集合(Immutable Collection)
Java 里的不可变集合不是简单地用 Collections.unmodifiableXXX() 包一层——那是“只读视图”,底层仍可被原引用修改,运行时才抛 UnsupportedOperationException。真正的不可变集合(如 ImmutableList、ImmutableSet)必须满足:内容不可变、结构不可变、线程安全、无隐藏可变状态。
Java 9+ 原生提供了 List.of()、Set.of()、Map.of() 等工厂方法,它们创建的是真正不可变的集合实例;而 Guava 的 ImmutableList.copyOf() 或 ImmutableList.builder() 也属此类。
Java 9+ List.of() 和 Collections.unmodifiableList() 的关键区别
二者行为差异直接影响程序健壮性:
-
List.of()返回的对象是紧凑、不可变、无空值(null元素直接抛NullPointerException)、不接受null的轻量实现;修改操作(如add()、set())会立即抛UnsupportedOperationException,且无法通过反射或内部字段绕过 -
Collections.unmodifiableList()只是包装了传入的List引用,若原始列表被其他代码修改(比如你把ArrayList传进去后又调用它的add()),这个“只读视图”会同步反映变化——它根本不是不可变,只是“禁止通过它改” - 性能上:
List.of()创建的是专用小容量实现(如ImmutableCollections.List12),无额外包装开销;而unmodifiableList()多一层代理对象
使用不可变集合时容易踩的坑
常见错误不是语法问题,而是对“不可变”的误判:
立即学习“Java免费学习笔记(深入)”;
- 误以为
Set.of("a", "b").add("c")会静默失败——实际编译就过不去(add()方法根本不存在),但开发者常在 IDE 补全时选错类型,拿到的是Set接口而非具体实现,导致编译期没报错、运行时报错 - 用
Map.ofEntries(Map.entry(k, v))构建 map 后,试图用put()修改——同样直接抛异常;但更隐蔽的是:如果entry中的 key 或 value 本身是可变对象(比如new StringBuilder("x")),那集合虽不可变,其元素状态仍可被外部修改 - 从数据库或 JSON 解析拿到
ArrayList,立刻用List.of(list.toArray())转——这没问题;但如果写成List.of(list),就会把整个ArrayList当作单个元素放进新列表,结果变成[oldArrayList]
什么时候该用不可变集合,什么时候不该用
核心判断依据是数据生命周期和共享场景:
- 适合用:
public static final常量集合(如 HTTP 状态码映射表)、配置项白名单、函数式编程中的中间结果(如 Stream.collect(Collectors.toUnmodifiableList()))、DTO 字段返回值(避免调用方意外污染) - 不适合用:需要频繁增删改的缓存容器、作为 Builder 模式中间状态、与遗留 API 交互(某些框架要求传
ArrayList实例才能反射设值) - 注意兼容性:Java 8 项目无法直接用
List.of(),得靠 Guava 或自行封装;而 Guava 的Immutable*类默认不支持null,若业务逻辑允许null元素,必须显式用ImmutableList.nullableCopyOf()
不可变性不是银弹——它解决的是“谁在什么时候能改什么”的控制权问题。一旦你把一个集合声明为不可变,就等于放弃了所有后续修改意图;如果那个集合本该承载变化,强行不可变只会让代码绕路、加锁、或复制,反而更重。










