两者都会在 add() 时抛异常,看起来"都是不可变的",但这篇真实验证发现它们在 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 的容忍度。
这是本次实验里最容易被误解的一点:"不可变"说的是这个视图对象本身不能被修改,不代表它背后的数据不会变。
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]
如果目的就是"给我一份不受原集合后续修改影响的不可变拷贝",真实应该用 List.copyOf()。
List.copyOf() 是真实快照,source变化后 snapshot 保持: [1, 2, 3] Map.of() 真实抛出 UnsupportedOperationException(put)
真实结果验证 List.copyOf() 在调用那一刻真实把元素复制进了一个新的、独立的不可变结构,之后原始 source 列表无论怎么修改,snapshot 都真实不受影响。Map.of() 作为同一族工厂方法,行为和 List.of() 一致,同样真实拒绝修改。
每一行都是真实运行这段代码得到的结果或异常。