bigywater.tech
Java 内部原理 · 65

不可变集合真实验证:List.of() 和 unmodifiableList() 的真实差异

两者都会在 add() 时抛异常,看起来"都是不可变的",但这篇真实验证发现它们在 null 处理和"是否真的独立于原集合"这两点上,行为真实完全不同。

List.of() 真实拒绝 null
真实验证:List.of(1, null, 3) 在构造阶段就真实抛出 NullPointerException,unmodifiableList() 却真实允许 null 元素
unmodifiableList 只是视图
真实验证:修改被包装的原始 List,unmodifiableList() 返回的"不可变"视图真实同步跟着变化

上一篇:枚举单例真实验证

真实验证:两者都真实拒绝修改,但拒绝 null 的态度不同

List.of() 是 Java 9 引入的工厂方法,Collections.unmodifiableList() 是更早就有的经典 API——先看基础的"拒绝修改"行为。

ImmutableCollectionsDemo.java List<Integer> listOf = List.of(1, 2, 3); listOf.add(4); // 抛异常 List<Integer> withNull = List.of(1, null, 3); // 构造阶段就抛异常
真实实测
List.of() 真实抛出 UnsupportedOperationException(add)
List.of() 真实抛出 NullPointerException(不允许null元素)
真实实测
unmodifiableList() 视图真实也抛出 UnsupportedOperationException(add)
unmodifiableList 真实允许 null 元素: [a, null, c]

真实结果证明了一个容易被忽视的差异:List.of() 在构造阶段就会真实检查每个元素,一旦发现 null 立刻抛出 NullPointerException——这是 JDK 9 以后不可变集合工厂方法的统一设计决策,目的是尽早暴露问题,而不是让 null 混进集合后在某个遥远的调用点才引发 NullPointerException。而 Collections.unmodifiableList() 是更老的 API,只是简单包装,真实继承了被包装集合本身对 null 的容忍度。

真实验证:unmodifiableList() 只是一层视图,不是快照

这是本次实验里最容易被误解的一点:"不可变"说的是这个视图对象本身不能被修改,不代表它背后的数据不会变。

ImmutableCollectionsDemo.java List<Integer> backing = new ArrayList<>(Arrays.asList(1, 2, 3)); List<Integer> unmodifiableView = Collections.unmodifiableList(backing); backing.add(999); // 直接修改底层list
真实实测
修改底层 backing 后,unmodifiableView 真实同步变化: [1, 2, 3, 999]
真实机制:视图,不是拷贝。Collections.unmodifiableList() 内部真实只是持有一个指向原始 backing 列表的引用,它的 get()、size() 等只读方法都真实转发给 backing,只有 add()/remove() 这类修改方法被真实拦截并抛出异常。这意味着如果你把这个"不可变视图"传给别人,自己手里的 backing 引用依然可以修改数据,对方拿到的视图会真实跟着变——这不是真正的不可变,只是"这个引用本身不能用来修改"。List.of() 则真实是内部持有一份独立数组的实现,没有暴露任何可以修改这份数据的入口。

真实验证:List.copyOf() 才是真正独立的快照

如果目的就是"给我一份不受原集合后续修改影响的不可变拷贝",真实应该用 List.copyOf()。

真实实测
List.copyOf() 是真实快照,source变化后 snapshot 保持: [1, 2, 3]
Map.of() 真实抛出 UnsupportedOperationException(put)

真实结果验证 List.copyOf() 在调用那一刻真实把元素复制进了一个新的、独立的不可变结构,之后原始 source 列表无论怎么修改,snapshot 都真实不受影响。Map.of() 作为同一族工厂方法,行为和 List.of() 一致,同样真实拒绝修改。

分步演示:三种"不可变"的真实强度差异

每一行都是真实运行这段代码得到的结果或异常。

List.of() 拒绝 null,unmodifiableList() 只是视图会跟着 backing 变,List.copyOf() 才是真正的快照
这一步新验证的调用

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,三组差异重复运行结果完全一致。
    • 和unmodifiable 视图那篇一起看,能更完整理解"不可变"在 Java 集合框架里其实是几种不同强度的保证,不是一个统一概念。
    • 没有涉及:List.of() 返回的具体实现类(ImmutableCollections 内部类,针对0/1/2个元素和更多元素有不同的优化实现)、Set.of() 对重复元素的真实检测机制——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号