你还在手动拆 record?Java 21 都能“解构匹配”了,你不心动吗?

张开发
2026/5/3 19:12:04 15 分钟阅读
你还在手动拆 record?Java 21 都能“解构匹配”了,你不心动吗?
你好欢迎来到我的博客我是【菜鸟不学编程】我是一个正在奋斗中的职场码农步入职场多年正在从“小码农”慢慢成长为有深度、有思考的技术人。在这条不断进阶的路上我决定记录下自己的学习与成长过程也希望通过博客结识更多志同道合的朋友。️ 主要方向包括 Java 基础、Spring 全家桶、数据库优化、项目实战等也会分享一些踩坑经历与面试复盘希望能为还在迷茫中的你提供一些参考。 我相信写作是一种思考的过程分享是一种进步的方式。如果你和我一样热爱技术、热爱成长欢迎关注我一起交流进步全文目录I. 记录模式Java 21 的 deconstruction解构II. 语法case Point(int x, int y)III. 嵌套模式递归模式匹配拆到你满意为止IV. 与 switch 结合exhaustive穷尽性检查V. 守卫子句when 条件过滤VI. 应用JSON 解析的模式匹配把 JSON 变成可解构的数据1定义一个 JSON AST可解构2用 Jackson 把 JsonNode 转成 JVal递归构建3重点来了对 JSON 结构做“模式匹配解析”小结把 record patterns 用出“像人写的代码”的味道 写在最后I. 记录模式Java 21 的 deconstruction解构Record Patterns 的核心能力在匹配时直接把 record 拆开deconstruct把组件值“解包”成局部变量。你以前要这样写if(objinstanceofPointp){intxp.x();intyp.y();// ...}现在可以更“声明式”一点匹配 解构一步到位if(objinstanceofPoint(intx,inty)){// x, y 已经就位}这种写法的微妙优势在于少了样板代码getter 提取那一段直接消失变量只在匹配成功的分支内有效作用域更干净复杂结构能靠“嵌套模式”一路拆下去后面会讲小吐槽以前写“拆对象”的代码像在拆快递现在更像是“快递到手直接开箱箱子还帮你分类好了”。II. 语法case Point(int x, int y)先给最小完整例子recordPoint(intx,inty){}staticStringdescribe(Objectobj){returnswitch(obj){casePoint(intx,inty)-Point(x,y);default-Not a point;};}这类switch模式匹配是 Java 21 正式特性Pattern Matching for switch。你可以把case Point(int x, int y)理解成一句非常直白的话“如果它是个Point那就把它拆成两个 int分别叫 x、y。”III. 嵌套模式递归模式匹配拆到你满意为止Record patterns最带劲的地方就是“可以嵌套”。JEP 440 明确提到 record patterns 与 type patterns 可嵌套从而实现更可组合的数据导航。看个稍微“像业务”的结构recordPoint(intx,inty){}recordRectangle(PointtopLeft,PointbottomRight){}以前你得if(objinstanceofRectangler){intx1r.topLeft().x();inty1r.topLeft().y();intx2r.bottomRight().x();inty2r.bottomRight().y();}现在可以一口气拆干净if(objinstanceofRectangle(Point(intx1,inty1),Point(intx2,inty2))){// 直接用 x1,y1,x2,y2}读起来像结构本身而不是像“如何从结构里挖数据”。这就是它的魅力更接近“描述”少一点“操作步骤”。IV. 与 switch 结合exhaustive穷尽性检查这块是 Java 21 里“专业味儿”最浓的收益之一编译器能帮你做穷尽性检查。尤其是你配合sealed 类型时switch会更可靠该写全的分支必须写全。Oracle 文档也强调了模式 switch 的安全性与覆盖性要求。来个典型写法用 sealed interface 表达“形状只有这几种”。sealedinterfaceShapepermitsCircle,Rect{}recordCircle(doubler)implementsShape{}recordRect(Pointa,Pointb)implementsShape{}recordPoint(intx,inty){}staticdoublearea(Shapes){returnswitch(s){caseCircle(doubler)-Math.PI*r*r;caseRect(Point(intx1,inty1),Point(intx2,inty2))-Math.abs((x2-x1)*(y2-y1));};}注意看没有 default。因为Shape是 sealed编译器知道它只有Circle/Rect两种实现你写全了就行如果你漏了一个编译器会提醒你——这在“长期维护”里非常值钱新增一种子类型时所有相关 switch 都会被迫更新不会悄悄漏逻辑。V. 守卫子句when条件过滤有时候“类型对了、结构也对了”但你还想加一点条件比如矩形必须是合法坐标x2 x1。Java 21 的 switch 模式匹配支持 guarded patterns守卫模式用when写条件过滤。staticStringclassify(Objecto){returnswitch(o){casePoint(intx,inty)when xy-Diagonal point: x;casePoint(intx,inty)-Point(x,y);default-Other;};}关键直觉when不是另一个 if它是 “case 的一部分”。也就是说只有匹配到了Point(int x, int y)才会再判断when。小提醒挺实用的把“更具体的 case”放前面。上面xy那条要写在普通 Point 前面否则永远轮不到它。VI. 应用JSON 解析的模式匹配把 JSON 变成可解构的数据先说句大白话Java 的 record pattern 不能“直接解构 Json 字符串”。它解构的是“record 实例”所以你的思路应该是先把 JSON 解析成一个你自己的 AST 模型sealed interface records再用 record patterns switch 对这个 AST 做结构化匹配像写规则引擎一样1定义一个 JSON AST可解构importjava.math.BigDecimal;importjava.util.List;importjava.util.Map;sealedinterfaceJValpermitsJNull,JBool,JNum,JStr,JArr,JObj{}recordJNull()implementsJVal{}recordJBool(booleanv)implementsJVal{}recordJNum(BigDecimalv)implementsJVal{}recordJStr(Stringv)implementsJVal{}recordJArr(ListJValitems)implementsJVal{}recordJObj(MapString,JValprops)implementsJVal{}2用 Jackson 把 JsonNode 转成 JVal递归构建示例使用 Jackson如果你用 Gson/Fastjson我也能换一版importcom.fasterxml.jackson.databind.JsonNode;importcom.fasterxml.jackson.databind.ObjectMapper;importjava.math.BigDecimal;importjava.util.*;publicclassJsonAst{staticfinalObjectMapperMnewObjectMapper();publicstaticJValparse(Stringjson)throwsException{JsonNodenM.readTree(json);returnfromNode(n);}staticJValfromNode(JsonNoden){if(n.isNull())returnnewJNull();if(n.isBoolean())returnnewJBool(n.booleanValue());if(n.isNumber())returnnewJNum(n.decimalValue());if(n.isTextual())returnnewJStr(n.textValue());if(n.isArray()){ListJVallistnewArrayList();for(JsonNodeit:n)list.add(fromNode(it));returnnewJArr(List.copyOf(list));}if(n.isObject()){MapString,JValmapnewLinkedHashMap();n.fields().forEachRemaining(e-map.put(e.getKey(),fromNode(e.getValue())));returnnewJObj(Map.copyOf(map));}// 兜底少见节点类型returnnewJStr(n.toString());}}3重点来了对 JSON 结构做“模式匹配解析”假设我们要解析一个用户对象结构像这样{type:user,id:7,name:Ava,tags:[vip,new]}我们可以写一个“结构化匹配”的解析器importjava.util.List;recordUser(longid,Stringname,ListStringtags){}staticUserparseUser(JValv){returnswitch(v){caseJObj(varprops)when props.get(type)instanceofJStr(Stringt)user.equals(t)props.get(id)instanceofJNum(varidNum)props.get(name)instanceofJStr(Stringname)-{longididNum.longValue();ListStringtagsswitch(props.get(tags)){caseJArr(varitems)-items.stream().filter(x-xinstanceofJStr).map(x-((JStr)x).v()).toList();casenull,default-List.of();};yieldnewUser(id,name,tags);}default-thrownewIllegalArgumentException(Not a user json);};}这段代码的“爽点”在于你写的不是一堆if (node.has(...))而是在写“结构规则”when可以把约束条件压在 case 上逻辑更聚合结构变化会非常显眼不再悄悄返回 null当然这里我写得偏“表达清晰”。如果你追求极致性能/更少装箱还可以把 Map lookup 和类型判断再做一次封装比如提取getStr/getNum工具函数但思想不变先建 AST再模式匹配。小结把 record patterns 用出“像人写的代码”的味道**记录模式JEP 440**让 record 解构变得声明式、可嵌套。**switch 模式匹配JEP 441**让分支更类型安全并能配合 sealed 做穷尽检查。when守卫让你把条件过滤写进 case 标签里读起来更“像规则”。JSON 场景最实用的套路是JSON → 自定义可解构 AST → switch record patterns 做结构解析可维护性很香。 写在最后如果你觉得这篇文章对你有帮助或者有任何想法、建议欢迎在评论区留言交流你的每一个点赞 、收藏 ⭐、关注 ❤️都是我持续更新的最大动力我是一个在代码世界里不断摸索的小码农愿我们都能在成长的路上越走越远越学越强感谢你的阅读我们下篇文章再见✍️ 作者某个被流“治愈”过的 Java 老兵 日期2025-08-25 本文原创转载请注明出处。

更多文章