Java中不可变代理的核心思路是不暴露原始集合引用,而是返回仅提供读取能力且无法修改的代理对象;它必须彻底断开与可变源的联系,通过复制快照或使用List.of()等原生不可变实现来确保安全,而非仅用Collections.unmodifiableList()做只读包装。

Java 中通过不可变代理保护底层集合数据,核心思路是:**不暴露原始集合引用,而是返回一个仅提供读取能力、且自身无法被修改的代理对象**。这种代理不是简单包装,而是切断所有写入通道,让调用方“看得到、动不了”。
不可变代理的本质是隔离而非包装
很多人误以为 Collections.unmodifiableList() 就是安全的不可变代理,其实不然——它只是返回一个只读视图,底层仍连着可变集合。只要有人还持有原始 ArrayList 引用,就能偷偷改数据,代理看到的就“变脏”了。
- 真正安全的不可变代理必须彻底断开与可变源的联系,比如复制一份快照,或直接使用 JDK 9+ 的
List.of()等原生不可变实现 - 代理类本身不能提供任何
add()、remove()、set()等修改方法;即使有,也必须抛出UnsupportedOperationException - 构造代理时若接收外部集合,应做防御性拷贝(
new ArrayList(source)),而非直接引用
两种主流实现方式对比
方式一:用 JDK 9+ of() 工厂方法(推荐)
直接创建真正不可变实例,无底层可变源,零风险。
List<String> safeNames = List.of("Alice", "Bob"); // 内部是紧凑不可变结构
// safeNames.add("Charlie"); // 编译可通过,运行时立即抛 UnsupportedOperationException
方式二:手动封装私有集合 + 只读接口
适合需要自定义逻辑或兼容旧版 JDK 的场景。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
立即学习“Java免费学习笔记(深入)”;
public class UserRegistry {
private final List<String> users = new ArrayList<>();
public List<String> getUsers() {
return List.copyOf(users); // Java 10+ 推荐:返回不可变副本
// 或 return Collections.unmodifiableList(new ArrayList<>(users)); // 兼容老版本
}
}
-
List.copyOf()是 Java 10 引入的安全替代方案,它内部自动做不可变封装,且比unmodifiableList()更可靠 - 避免返回
Arrays.asList()结果——它支持set()但不支持add(),属于“半可变”,极易踩坑
关键细节:null 和线程安全怎么处理
不可变代理不仅要防写,还要防非法输入和并发干扰:
-
List.of()和Set.of()明确禁止null元素,传入即抛NullPointerException,这在构造期就守住底线 - 真正的不可变代理天然线程安全——没有写操作,就不需要同步,多个线程同时遍历完全无竞争
- 如果底层数据可能被其他模块修改(如共享缓存),代理必须基于某一时刻的快照构建,而不是实时绑定
为什么不用代理类自己继承 ArrayList?
有人试图写个子类重写所有修改方法来“假装不可变”,这是危险的:
- 子类仍能被向下转型为
ArrayList,绕过限制 - 违反里氏替换原则:子类行为与父类契约冲突
- JDK 的不可变集合(如
ImmutableCollections.ListN)是独立实现类,不继承任何可变集合,从根源杜绝越权

















