Java 记录模式怎样拆解嵌套数据对象
来源:17golang原创
时间:2026-10-09 05:32:00 308浏览 收藏
Java 记录模式(record pattern)可以把“检查对象是不是某种 record”和“提取 record 组件”合并成一次模式匹配。面对 Order -> Customer -> Address 这样的嵌套数据,不必先保存订单、客户、地址三个临时变量,可以直接在一个模式中递归拆解到城市、街道等叶子字段。
记录模式在 Java 21 中成为正式特性,适用于 instanceof、switch 语句和 switch 表达式。它只负责结构检查与组件提取,不会自动完成业务校验,也不会把普通 class 变成可解构对象。
先确认升级范围和适用对象
| 检查项 | 迁移前 | 记录模式写法 |
|---|---|---|
| 最低版本 | 旧版本可用 record,但不能使用正式记录模式 | Java 21 及以上 |
| 目标类型 | 手动调用访问器 | 目标必须匹配 record 类型 |
| 嵌套提取 | 逐层创建局部变量 | 模式可递归嵌套 |
| null | 手动判空 | null 不匹配任何记录模式 |
| 泛型 | 常见强制转换风险 | 不能依赖未经检查的转换 |
官方语言指南把记录模式定义为“记录类型 + 可能为空的组件模式列表”。匹配成功后,JVM 会通过记录组件访问器取得值,并初始化模式变量。需要迁移的是读取路径,而不是 record 自身的构造和校验规则。
从逐级访问器改成最小记录模式
先看单层数据。传统写法要先判断类型,再调用访问器;记录模式把两件事放在同一个条件里。
record Point(double x, double y) {}
static double distanceFromOrigin(Object value) {
// 匹配 Point 后直接提取两个组件,变量只在条件成立的分支内可用
if (value instanceof Point(double x, double y)) {
return Math.hypot(x, y);
}
// 非 Point 对象保留明确的失败语义
throw new IllegalArgumentException("Point required");
}
如果组件类型很长,可以使用 var:
static String formatPoint(Object value) {
// 编译器从 Point 的组件声明推断 x 和 y 都是 double
if (value instanceof Point(var x, var y)) {
return "(" + x + ", " + y + ")";
}
// 未匹配时返回调用方可识别的占位值
return "not-a-point";
}
var 只省略局部模式变量的显式类型,不会放宽匹配规则。代码评审时,简单 record 用 var 往往更紧凑;当同一位置可能出现多个相近类型时,显式写类型更容易读。

在外层模式里继续拆解内层 record
嵌套记录模式的关键是:record 组件本身如果仍是 record,就可以在对应位置继续放入记录模式。下面的数据结构包含订单、客户和地址三层。
import java.math.BigDecimal;
record Address(String city, String street) {}
record Customer(String name, Address address) {}
record Order(String id, Customer customer, BigDecimal total) {}
static String shippingLabel(Object value) {
// 模式结构与 record 组件结构一一对应,并直接绑定叶子字段
if (value instanceof Order(
var orderId,
Customer(var customerName, Address(var city, var street)),
var total)) {
return orderId + " | " + customerName + " | " + city + street
+ " | " + total;
}
// 类型不匹配或任一嵌套 record 为 null 时进入失败分支
return "unsupported-order";
}
这里没有 order.customer().address().city() 的访问器链,也不需要为中间对象起临时变量。模式形状就是数据形状:Order 的第二个组件对应 Customer,而 Customer 的第二个组件继续对应 Address。
不需要继续拆解的部分可以保留成变量。例如只关心客户地址,不关心订单金额的内部字段时,仍可把整个金额组件绑定为 total,也可以用未命名模式能力处理不使用的组件,但那涉及不同 Java 版本的特性范围;为了让 Java 21 项目直接采用,本文示例保持普通变量写法。
把多种嵌套结构交给 switch 表达式
当输入存在多种合法结构时,switch 比连续的 if instanceof 更容易看清覆盖范围。记录模式可以和 sealed 类型一起使用,让编译器帮助检查分支是否完整。
import java.math.BigDecimal;
sealed interface Event permits OrderCreated, CustomerMoved {}
record OrderCreated(Order order) implements Event {}
record CustomerMoved(Customer customer) implements Event {}
static String summarize(Event event) {
return switch (event) {
// 一次拆解事件、订单、客户和地址,直接获得摘要所需字段
case OrderCreated(Order(
var orderId,
Customer(var name, Address(var city, var street)),
BigDecimal total)) ->
"new order " + orderId + " for " + name
+ " in " + city + ", total=" + total;
// 另一种事件复用相同的 Customer 与 Address 模式结构
case CustomerMoved(Customer(
var name,
Address(var city, var street))) ->
name + " moved to " + city + street;
};
}
这种写法适合事件、命令、查询结果和语法树节点等封闭模型。它的收益不是单纯少写几行,而是把“允许出现的结构”和“从结构里取什么”放在同一个分支中。若模型不是 sealed,或业务允许未知实现,则仍要提供 default 分支。

null、泛型和嵌套失败要单独处理
null 不会匹配记录模式
无论最外层目标是 null,还是某个需要继续拆解的嵌套 record 组件为 null,对应的记录模式都不会匹配。对 instanceof 来说,程序会进入 else 或后续逻辑;对 switch 来说,如果输入本身可能为 null,应明确设计 case null 或在进入 switch 前处理,不能把记录模式当成判空替代品。
static String cityOf(Object value) {
return switch (value) {
// 输入本身为 null 时给出独立结果,避免语义含糊
case null -> "unknown";
// Address 组件为 null 时,嵌套记录模式不会匹配这个分支
case Customer(var name, Address(var city, var street)) -> city;
// 兜底处理其他类型以及嵌套结构不完整的对象
default -> "unsupported";
};
}
泛型匹配不能依赖未经检查的转换
泛型 record 可以使用记录模式,但前提是目标值能够安全转换为模式中的参数化类型。如果只有原始类型 Box,就不能写 Box 并期待编译器执行不安全的运行时泛型检查。更稳妥的做法是让方法参数保留完整泛型信息。
record Box(T value) {} static int textLength(Box box) { // 参数已经保留 Box 类型,组件模式可以安全推断为 String if (box instanceof Box(var text)) { return text.length(); } // 当前静态类型下通常可匹配,保留异常分支表达方法契约 throw new IllegalArgumentException("String box required"); }
迁移旧访问器链时逐项回归
- 确认构建、CI 与生产运行时都在 Java 21 或更高版本,不再依赖预览参数。
- 先从纯读取代码迁移,例如格式化、路由、查询结果映射和事件摘要。
- 让模式结构严格对应 record 组件顺序;组件改名不影响位置,但组件重排会改变模式含义。
- 为最外层 null、嵌套 record 为 null、类型不匹配和未知实现保留明确分支。
- 泛型场景保持完整参数化类型,不用原始类型绕过编译器。
- 嵌套超过三层且一行难以阅读时,拆成命名方法或先匹配外层再匹配内层。
- 对 sealed switch 检查新增实现后的编译失败,把它当作模型覆盖变更提示。
记录模式最适合稳定、只读的数据形状。它能显著压缩访问器链,但不应该把所有业务逻辑塞进一条巨大的模式。只提取当前分支真正需要的字段,嵌套层次过深时及时拆开,代码通常会比“把所有组件一次解构完”更耐维护。
常见问题
普通 Java 类能用记录模式拆解吗?
不能。记录模式针对 record 类型及其组件。普通类仍可使用类型模式先匹配类型,再调用公开方法读取数据。
记录模式会直接读取字段吗?
官方说明中,记录模式通过 record 的组件访问器提取值。理解这一点有助于把它看成结构化的访问器调用,而不是突破封装直接读字段。
嵌套 Address 为 null 会得到 city=null 吗?
不会。null 不匹配记录模式,因此 Address(var city, var street) 整体失败,程序会进入其他分支。
所有组件都应该写成 var 吗?
不必。类型明显且名称清楚时使用 var 很简洁;需要强调边界、区分相近类型或帮助评审时,写出显式类型更合适。
官方资料
Java 25 模式匹配指南:https://docs.oracle.com/en/java/javase/25/language/pattern-matching.html
Java 21 记录模式指南:https://docs.oracle.com/en/java/javase/21/language/record-patterns.html
JEP 440:https://openjdk.org/jeps/440
-
479 收藏
-
337 收藏
-
128 收藏
-
149 收藏
-
202 收藏
-
265 收藏
-
370 收藏
-
457 收藏
-
278 收藏
-
文章 · java教程 | 12小时前 | Java · 异常处理 · AutoCloseable Java try-with-resources suppressed exception 关闭顺序 getSuppressed197 收藏
-
462 收藏
-
212 收藏
-
446 收藏
-
414 收藏
-
文章 · java教程 | 1天前 | Java · 性能优化 · Stream · Java教程 · Java Stream Spliterator 并行流 parallelStream Stream副作用405 收藏
-
270 收藏
-
文章 · java教程 | 1天前 | 数据校验 · api设计 · Java教程 · 参数校验 Java record API DTO Jakarta Validation 紧凑规范构造器 跨字段校验370 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习