Collections.unmodifiableList() 抛 UnsupportedOperationException 是因为其返回的包装类(如UnmodifiableRandomAccessList)重写了所有修改方法并直接抛异常,仅提供运行时防护而非冻结数据;Java 9+ 推荐使用 List.of() 等创建实例级不可变集合,具备结构固化、无包装开销、不可反射绕过等优势。

为什么 Collections.unmodifiableList() 修改时抛 UnsupportedOperationException
因为底层返回的是一个包装类(如 UnmodifiableRandomAccessList),它把所有修改操作(add()、set()、clear() 等)都重写为直接抛出 UnsupportedOperationException,不转发给原集合。原集合本身仍可变,只读视图只是“拦住调用”,不是“冻结数据”。
常见错误现象:以为加了 unmodifiableList() 就绝对安全,结果原集合被其他地方修改,视图内容悄悄变了;或者误以为能防止 removeIf() 这类方法,其实它也会抛异常——只要方法签名在接口中声明为修改行为,包装类就拦截。
- 只读视图 ≠ 不可变:原集合改,视图立刻反映变化
- 所有修改方法(包括
replaceAll()、sort())都会抛UnsupportedOperationException - 不阻止并发修改:多线程下仍可能触发
ConcurrentModificationException(因底层迭代器未做额外同步)
Java 9+ 推荐用 List.of() 创建真正不可变集合
List.of()、Set.of()、Map.of() 返回的是紧凑、高效、**实例级不可变**的集合,内部无包装、无代理,连反射都难绕过(JDK 内部用私有构造 + 静态工厂,且元素数组 final)。和 Collections.unmodifiableXxx() 本质不同:后者是运行时防护,前者是编译时契约 + 内存结构固化。
使用场景:配置项、常量集合、函数返回值、DTO 中的固定列表(比如 HTTP 响应里的状态码枚举列表)。
立即学习“Java免费学习笔记(深入)”;
- 空集合必须用
List.of(),不能传null元素(会抛NullPointerException) - 重复元素在
Set.of()中会立即抛IllegalArgumentException - 性能更好:无包装对象开销,内存占用约减少 30%~50%(尤其小集合)
- 不支持
null—— 这是设计约束,不是 bug
unmodifiableXxx() 和 of() 混用时容易踩的坑
最典型:先用 List.of(1, 2, 3) 得到不可变列表,再传给 Collections.unmodifiableList() —— 完全多余,还多套一层对象。更危险的是反向操作:对 unmodifiableList() 返回值再调用 stream().toList()(Java 16+),你以为得到新不可变集合,其实它只是浅拷贝,底层数组仍指向原视图,而 toList() 返回的是 ImmutableCollections.ListN(JDK 内部类),和 of() 的实现不同,但行为一致;不过若中间混入 new ArrayList(...),就彻底失去不可变性。
- 不要嵌套包装:
Collections.unmodifiableList(List.of(...))白费力气 - 别用
new ArrayList(unmodifiableList)试图“复制”——这会创建可变副本,且丢失原始不可变语义 - 流式操作后需显式转回不可变:
list.stream().filter(...).collect(Collectors.toUnmodifiableList())(Java 10+) -
Arrays.asList()返回的列表本身可变,即使再套unmodifiableList(),也不能阻止原数组被修改(因它底层数组引用未拷贝)
需要深不可变?得自己处理嵌套对象
Java 的不可变集合只保证容器结构不可变,不递归冻结元素。如果集合里存的是自定义对象(比如 User 实例),哪怕集合本身是 List.of(),只要 User 是可变的,外部仍可通过 list.get(0).setName("xxx") 改变状态。
这没有银弹。要么让元素类型本身不可变(final 字段 + 无 setter),要么在构建集合时做防御性拷贝(如 list.stream().map(User::copy).collect(toUnmodifiableList())),要么用第三方库(如 Immutables 或 Vavr)生成真正深不可变结构。
-
toUnmodifiableList()不等于 “deep freeze” - JSON 序列化/反序列化常意外暴露可变性(如 Jackson 默认用 setter 构造对象)
- 单元测试里别只测集合长度和顺序,要验证元素字段是否真没被改过
unmodifiableXxx),一层是结构固化(of())。选哪层,取决于你信不信“别人不会动我的原集合”,以及你愿不愿意为元素的不可变性多写几行代码。










