
Java 记录虽默认具备结构不可变性,但若字段为可变集合(如 List),外部仍可通过返回的引用修改其内容;需在构造时深拷贝并封装为不可修改视图,才能实现真正意义上的不可变性。
java 记录虽默认具备结构不可变性,但若字段为可变集合(如 `list
在 Java 中,record 的核心设计目标之一是“语义不可变性”——即一旦创建,其逻辑状态不应被更改。然而,仅声明 record Foo(List<string> bar) { }</string> 是远远不够的:它只保证 bar 引用本身不可重赋值,却不阻止对所指向 List 实例的内部修改(例如 foo.bar().add("x"))。这种“浅不可变”极易引发隐蔽的并发问题或逻辑错误,尤其在多线程环境或作为 Map 键、Set 元素使用时。
要达成真正不可变(deep immutability),关键在于两点:
- 防御性拷贝(Defensive Copy):在构造时复制传入集合的内容,切断与原始集合的引用关联;
-
返回不可修改视图(Unmodifiable View):确保
bar()访问器返回的始终是只读包装,任何修改操作均抛出UnsupportedOperationException。
JDK 10+ 提供了简洁高效的方案:List.copyOf()。它原子性地完成两项任务——创建新列表并返回不可修改的不可变副本(注意:该副本在 JDK 17+ 中是紧凑、高效且不可序列化修改的)。推荐写法如下:
public record Foo(List<String> bar) {
public Foo {
// ✅ 安全:copyOf 创建新列表 + 不可修改包装,且自动校验 null 元素
this.bar = List.copyOf(bar);
}
}⚠️ 注意事项:
立即学习“Java免费学习笔记(深入)”;
List.copyOf()要求输入Collection非null,且所有元素非null;若需容忍null,应改用Collections.unmodifiableList(new ArrayList(bar))并手动处理空值;- 若集合元素类型本身可变(如
List<mutableperson></mutableperson>),仅保护外层List不够——还需确保MutablePerson自身不可变,或对其做深度克隆(此时建议使用不可变对象模型,如record Person(String name, int age));- 同理适用于
Set和Map:分别使用Set.copyOf()和Map.copyOf()(JDK 10+ / JDK 16+)。
验证不可变性的单元测试至关重要。以下断言覆盖三大核心场景:
- 记录内列表内容与构造时一致;
- 修改原始列表不影响记录内列表;
- 尝试修改记录内列表抛出
UnsupportedOperationException。
@Test
void recordListIsTrulyImmutable() {
var original = new ArrayList<>(List.of("a", "b", "c"));
var foo = new Foo(original);
// 1. 内容一致
assertEquals(List.of("a", "b", "c"), foo.bar());
// 2. 原始列表变更不影响记录
original.add("d");
assertEquals(List.of("a", "b", "c"), foo.bar());
// 3. 记录内列表不可修改
assertThrows(UnsupportedOperationException.class, () -> foo.bar().clear());
}综上,不应对集合字段的可变性视而不见。在构建不可变记录时,必须主动防御——利用 List.copyOf() 等标准 API 实现轻量、安全、符合 JDK 最佳实践的深不可变保障。这是写出健壮、可维护、线程安全数据载体的必要步骤。


















