Java List.copyOf 怎样创建不可变副本
在 Java 方法之间传递一组配置、权限或查询条件时,如果直接把 ArrayList 交给下游代码,接收方就可能改变原列表。List.copyOf 适合解决这个边界:它按源集合的迭代顺序创建一个不接受增删改的列表,源集合之后再追加元素也不会改变这个列表的结构。
官方地址:https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/List.html
List.copyOf(source)返回不可修改列表,调用add、remove或set会抛出UnsupportedOperationException。- 输入集合或其中任意元素为
null时,创建副本会抛出NullPointerException。 - 它主要隔离列表结构和源集合,不会递归复制元素对象;元素自身可变时,仍要单独设计对象边界。
List.copyOf 到底复制了什么
List.copyOf 接收的是 Collection extends E>,返回一个包含相同元素的 List。元素会按照源集合迭代器产生的顺序进入结果列表,所以源集合是 LinkedHashSet 或有明确顺序的列表时,顺序也会被保留下来。这里的“副本”首先指列表容器的独立状态,不等于对每个元素做深拷贝。
import java.util.ArrayList;
import java.util.List;
public class ListCopyOfDemo {
public static void main(String[] args) {
var source = new ArrayList();
source.add("read"); // 先在可变源集合中准备权限
source.add("write");
var snapshot = List.copyOf(source); // 按迭代顺序创建不可变副本
source.add("delete"); // 源集合继续变化,不会把新元素推入 snapshot
System.out.println(source); // [read, write, delete]
System.out.println(snapshot); // [read, write]
}
}
这个结果适合用在“调用方继续组装,接收方只读”的场景。例如控制器收到一组筛选字段后,可以在进入服务层前转成副本;服务层无需担心某个辅助方法偷偷调用 clear 或 add 改掉调用方的数据。

副本不能改,元素对象却不一定不可变
Oracle API 对 List.of 和 List.copyOf 创建的列表都规定了不可修改、禁止 null 元素等特性。这里的不可修改是列表结构层面的约束:不能替换位置上的引用,也不能增加或移除元素。若元素是一个可变对象,仍然可以通过元素引用修改它的内部字段。
import java.util.ArrayList;
import java.util.List;
record MutableLabel(StringBuilder text) {}
public class ElementBoundaryDemo {
public static void main(String[] args) {
var label = new MutableLabel(new StringBuilder("draft"));
var snapshot = List.copyOf(new ArrayList(List.of(label))); // 只固定列表结构
label.text().append("-changed"); // 元素内部仍可变,副本能观察到同一个对象的变化
System.out.println(snapshot.get(0).text()); // draft-changed
// 列表位置不能替换;这行会抛出 UnsupportedOperationException。
// snapshot.set(0, new MutableLabel(new StringBuilder("new")));
}
}
因此,若业务需要真正的只读快照,元素类型也应使用不可变类型,例如字符串、不可变记录或创建了防御性副本的值对象。只把外层 List 换成 copyOf,不能自动保护嵌套的 Map、数组或自定义可变对象。
为什么 null 会让 List.copyOf 直接失败
List.copyOf 不接受空集合引用,也不接受含有 null 元素的集合。它在创建阶段就会拒绝这些输入,而不是生成一个以后再检查的特殊列表。这个行为对配置和权限列表很有用:空值问题会在边界处暴露,不会等到下游遍历时才产生含糊的错误。
import java.util.ArrayList;
import java.util.List;
public class NullInputDemo {
static List freeze(List input) {
if (input == null) {
return List.of(); // 业务若允许“没有配置”,先明确转成空列表
}
return List.copyOf(input); // 输入含 null 时仍会抛出 NullPointerException
}
public static void main(String[] args) {
var input = new ArrayList();
input.add("safe");
// input.add(null); // 不要把 null 静默混入需要冻结的配置
System.out.println(freeze(input));
}
}
是否把 null 当成空列表,应该由接口契约决定。上例的 freeze 只演示一种选择:方法参数本身为 null 时使用空列表;但集合内部出现 null 仍然保留失败,让调用方修复具体数据,而不是把缺失值悄悄变成字符串或默认权限。

把 List.copyOf 放在什么接口边界
一个实用位置是“接收输入后立即固化”。调用方可以继续修改自己的 ArrayList,而被调用方保存的是当时的顺序和元素引用。返回值则可以直接暴露为 List,不必让调用者知道内部究竟用了哪种不可修改实现。
import java.util.List;
final class QueryPlan {
private final List fields;
QueryPlan(List fields) {
this.fields = List.copyOf(fields); // 构造时固定列表结构,避免外部后续增删
}
List fields() {
return fields; // 返回不可修改列表,调用方不能反向改变计划结构
}
}
public class BoundaryDemo {
public static void main(String[] args) {
var fields = new java.util.ArrayList(List.of("id", "name"));
var plan = new QueryPlan(fields);
fields.add("created_at"); // 外部源集合变化不影响已构造的 plan
System.out.println(plan.fields()); // [id, name]
}
}
这个做法并不自动解决线程安全,也不代表元素内部没有共享引用。如果 fields 里放的是可变配置对象,仍然要在对象层做复制或改成不可变值类型。对普通字符串、枚举和不可变记录,List.copyOf 往往已经足够清晰。
选择 copyOf、unmodifiableList 还是可变副本
| 需求 | 更合适的写法 | 关键区别 |
|---|---|---|
| 创建结构不可变的列表副本 | List.copyOf(source) | 源集合后续结构变化不影响结果,拒绝 null 元素 |
| 只包一层只读视图 | Collections.unmodifiableList(source) | 源集合变化会反映到视图,仍共享同一个容器 |
| 需要继续增删改 | new ArrayList(source) | 获得可变容器,适合后续编辑 |
| 元素也要隔离 | 复制元素后再组装列表 | 需要按元素类型设计深拷贝或不可变值对象 |
最容易混淆的是 List.copyOf 与 Collections.unmodifiableList。前者创建自己的列表结构,后者只是给原列表加一层“不能通过这个引用修改”的视图。若原列表之后追加元素,视图可能看到新元素,而 copyOf 的结果不会跟着扩展。
使用前的四项检查
- 确认业务要的是“结构不可变副本”,而不是会随源列表变化的只读视图。
- 确认输入集合引用和每个元素都满足空值策略;需要允许
null时,先转换成业务明确的默认值。 - 确认元素本身是否可变;如果是嵌套集合、数组或可变 DTO,单层
copyOf不足以做深度快照。 - 确认所有修改操作都发生在复制前;副本创建后只把它当成读取接口使用。
两个容易混淆的问题
List.copyOf 会不会把源 ArrayList 的后续新增元素带过来?
不会。结果列表拥有独立的列表结构,创建完成后源集合再新增或移除元素,不会改变结果的元素数量和顺序。不过,若元素对象本身可变,结果中仍可能看到同一个对象的字段变化。
List.copyOf 和 Collections.unmodifiableList 哪个更安全?
要看边界。需要一次性的结构副本时选 List.copyOf;需要一个实时只读视图时才考虑 Collections.unmodifiableList。两者都不是深度复制,也都不能替代对可变元素和并发访问的设计。
Java LinkedHashMap 怎样用 accessOrder 实现最近最少使用淘汰
- 上一篇
- Java LinkedHashMap 怎样用 accessOrder 实现最近最少使用淘汰
- 下一篇
- Java Stream mapMulti 怎样展开少量结果
-
- 文章 · java教程 | 1小时前 | Java · java jcmd Thread.print 线程栈
- Java 怎样用 jcmd 查看线程栈
- 339浏览 收藏
-
- 文章 · java教程 | 1小时前 | java arguments ProcessHandle ProcessHandle.Info commandLine 进程诊断
- Java ProcessHandle.Info 怎样读取进程命令信息
- 205浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java JDBC 怎样让 ResultSet 随语句自动关闭
- 208浏览 收藏
-
- 文章 · java教程 | 1小时前 | java 时间处理 INSTANT truncatedTo ChronoUnit
- Java Instant 怎样按分钟截断时间
- 322浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java HttpClient 怎样为单个请求设置超时
- 454浏览 收藏
-
- 文章 · java教程 | 1小时前 | Java · 并发编程 · java 并发控制 Semaphore 资源限流 tryAcquire
- Java Semaphore 怎样限制并发资源数
- 133浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java 怎样用虚拟线程执行阻塞任务
- 481浏览 收藏
-
- 文章 · java教程 | 1小时前 | java shutdown 线程池 ExecutorService close awaitTermination
- Java ExecutorService close 怎样等待任务收尾
- 152浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java Stream mapMulti 怎样展开少量结果
- 493浏览 收藏
-
- 文章 · java教程 | 1小时前 | 缓存 · Java · 集合 · java linkedhashmap LRU缓存 removeEldestEntry accessOrder
- Java LinkedHashMap 怎样用 accessOrder 实现最近最少使用淘汰
- 115浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 523次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 580次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 607次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 557次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 377次使用
-
- Go Java 算法之字符串解码示例详解
- 2023-01-07 479浏览
-
- Go Java算法之单词搜索示例详解
- 2022-12-30 337浏览
-
- Gojava算法之括号生成示例详解
- 2023-02-22 128浏览
-
- GoJava算法之累加数示例详解
- 2023-01-07 149浏览
-
- GoJava算法最大单词长度乘积示例详解
- 2023-01-12 202浏览

