Java 21模式匹配重构实战:把200行订单处理逻辑压缩到50行以内

2026-07-03 0 419

上周被分到一个工单,要求给订单处理模块增加一种新的支付方式。打开代码一看,满屏的instanceof和层层嵌套的if-else,光搞清楚处理流程就花了我整整一个下午。改完业务之后实在忍不了,花了一天时间用Java 21的模式匹配和Record把整个模块翻了一遍。改完之后代码行数从200多行掉到50行不到,bug还不小心顺带修了两个。趁这次经历还新鲜,把重构的步骤和方法完整记下来。

先看看原来的代码长什么样

这个订单模块的核心逻辑是根据不同的订单类型和支付状态,执行对应的处理流程。简化后的旧代码大概是这样:

                
public void processOrder(Object order) {
    if (order instanceof OnlineOrder) {
        OnlineOrder online = (OnlineOrder) order;
        if (online.getPaymentStatus() == PaymentStatus.PAID) {
            // 已支付在线订单的处理逻辑
            fulfillOnlineOrder(online);
        } else if (online.getPaymentStatus() == PaymentStatus.PENDING) {
            // 待支付在线订单的处理逻辑
            remindPayment(online);
        } else if (online.getPaymentStatus() == PaymentStatus.CANCELLED) {
            cancelOnlineOrder(online);
        }
    } else if (order instanceof OfflineOrder) {
        OfflineOrder offline = (OfflineOrder) order;
        if (offline.getPaymentStatus() == PaymentStatus.PAID) {
            fulfillOfflineOrder(offline);
        } else if (offline.getPaymentStatus() == PaymentStatus.PENDING) {
            holdOfflineStock(offline);
        }
    } else if (order instanceof PreOrder) {
        PreOrder pre = (PreOrder) order;
        if (pre.getPaymentStatus() == PaymentStatus.PAID) {
            scheduleProduction(pre);
        } else if (pre.getPaymentStatus() == PaymentStatus.PENDING) {
            notifyPreOrderPending(pre);
        }
    } else {
        throw new IllegalArgumentException("未知订单类型");
    }
}
                
            

这还只是三种订单类型、几种支付状态的组合,实际项目里还有退款订单、换货订单、虚拟商品订单等等,状态也远不止三个。每加一种新类型,这个方法的行数就指数级膨胀。更要命的是,不同的人往里面加逻辑,各自的命名习惯和判断顺序都不一样,时间一长完全变成了一锅粥。

团队里几次提出要重构,但一看到这么多分支就头疼,没人愿意碰。这次趁Java 21已经在生产环境跑稳了,我决定用模式匹配和Record把这块彻底理顺。

第一步:用Record替代普通POJO

原来的订单类都是用@Data注解的普通JavaBean,Getter、Setter、构造器一大堆。这本身不是问题,但订单对象在创建后其实不应该再被修改,用普通Bean没法表达“不可变”这个语义,容易在后续处理中被误改。

我把三个订单类全改成了Record:

                
public record OnlineOrder(String orderId, PaymentStatus paymentStatus, String paymentMethod) {}
public record OfflineOrder(String orderId, PaymentStatus paymentStatus, String storeCode) {}
public record PreOrder(String orderId, PaymentStatus paymentStatus, LocalDate expectedDate) {}
                
            

一行代码搞定,构造器、Getter、equals、hashCode、toString全部自动生成,而且天然不可变。外面的处理逻辑再也不用担心不小心改了订单状态。

第二步:用模式匹配消灭instanceof和强制转型

旧的instanceof后面必须跟一个强制转型,又啰嗦又容易出错。Java 16开始引入的模式匹配可以把这两步合二为一,到了Java 21这个特性已经非常成熟。原来的代码可以改成:

                
if (order instanceof OnlineOrder online) {
    // 直接用online,不需要再转型
    if (online.paymentStatus() == PaymentStatus.PAID) {
        fulfillOnlineOrder(online);
    }
    // ...
}
                
            

这一步虽然看着只是少了一行转型,但消除的是长期以来Java开发者一直默默承受的小痛点。更重要的是,模式匹配为下一步 switch 的改造铺好了路。

第三步:用switch表达式加模式匹配一次性处理所有分支

Java 21的switch已经完全支持模式匹配,可以直接对对象类型和属性值进行匹配。重构后的核心逻辑变成了这样:

                
public void processOrder(Object order) {
    switch (order) {
        case OnlineOrder online && online.paymentStatus() == PaymentStatus.PAID ->
            fulfillOnlineOrder(online);
        case OnlineOrder online && online.paymentStatus() == PaymentStatus.PENDING ->
            remindPayment(online);
        case OnlineOrder online && online.paymentStatus() == PaymentStatus.CANCELLED ->
            cancelOnlineOrder(online);
        case OfflineOrder offline && offline.paymentStatus() == PaymentStatus.PAID ->
            fulfillOfflineOrder(offline);
        case OfflineOrder offline && offline.paymentStatus() == PaymentStatus.PENDING ->
            holdOfflineStock(offline);
        case PreOrder pre && pre.paymentStatus() == PaymentStatus.PAID ->
            scheduleProduction(pre);
        case PreOrder pre && pre.paymentStatus() == PaymentStatus.PENDING ->
            notifyPreOrderPending(pre);
        default ->
            throw new IllegalArgumentException("未处理的订单类型或状态");
    }
}
                
            

这里用到了Java 21新增的when子句(用&&连接),可以在模式匹配的同时检查属性值。每个分支的意图一眼就能看清,不再需要翻阅嵌套结构。而且因为有default分支兜底,编译器还能检查出没有覆盖的组合,避免漏掉处理逻辑。

第四步:引入密封类,让编译器帮我们检查遗漏

上面的switch虽然清晰,但Object order这个入参类型太宽泛了,谁都能往里面扔一个奇怪的东西。用密封类可以限定订单类型的范围,而且编译器还会强制我们处理所有子类型。

定义一个密封接口:

                
public sealed interface Order permits OnlineOrder, OfflineOrder, PreOrder {
    String orderId();
    PaymentStatus paymentStatus();
}
                
            

让三个Record实现这个接口:

                
public record OnlineOrder(String orderId, PaymentStatus paymentStatus, String paymentMethod) implements Order {}
public record OfflineOrder(String orderId, PaymentStatus paymentStatus, String storeCode) implements Order {}
public record PreOrder(String orderId, PaymentStatus paymentStatus, LocalDate expectedDate) implements Order {}
                
            

然后把processOrder的入参类型从Object改成Order,switch里的default分支就可以去掉了,因为编译器知道所有可能的类型都已经列出。如果以后有人新增一个RefundOrder类,只要不改permits列表,编译器就会报错,强制提醒修改这里的处理逻辑。

最终版本:清晰得像一份说明书

把以上几步全部做完之后,最终的processOrder方法变成了下面这个样子:

                
public void processOrder(Order order) {
    switch (order) {
        case OnlineOrder online when online.paymentStatus() == PaymentStatus.PAID ->
            fulfillOnlineOrder(online);
        case OnlineOrder online when online.paymentStatus() == PaymentStatus.PENDING ->
            remindPayment(online);
        case OnlineOrder online when online.paymentStatus() == PaymentStatus.CANCELLED ->
            cancelOnlineOrder(online);
        case OfflineOrder offline when offline.paymentStatus() == PaymentStatus.PAID ->
            fulfillOfflineOrder(offline);
        case OfflineOrder offline when offline.paymentStatus() == PaymentStatus.PENDING ->
            holdOfflineStock(offline);
        case PreOrder pre when pre.paymentStatus() == PaymentStatus.PAID ->
            scheduleProduction(pre);
        case PreOrder pre when pre.paymentStatus() == PaymentStatus.PENDING ->
            notifyPreOrderPending(pre);
    }
}
                
            

注意这里用了when关键字而不是之前的&&。Java 21最终版把&&改成了when,语义上更明确:这是模式匹配的守卫条件,而不是普通的布尔操作。我最初写的时候还是用&&,编译器报了个预发行API的警告,换成when之后才彻底合规。

整个方法从200多行缩到了不到20行,而且每个业务分支的对应关系一目了然。新加入的同事看了五分钟就明白整个订单处理框架,这在以前根本不敢想。

迁移过程中的几个小插曲

重构过程还算顺利,但也踩了几个坑:

  • 同时匹配两个属性值的写法。一开始我尝试写成case OnlineOrder(PaymentStatus.PAID) online,以为能直接解构Record,结果发现Java 21还不支持Record模式解构,这个功能还在预览。当前只能用when子句检查属性值。不过听说Java 22已经把Record模式转正了,后续可以直接解构。
  • 枚举值的导入。switch的分支里如果使用枚举值,必须写全限定名或者提前静态导入,否则编译器会报错。这点一开始让我卡了十分钟,查了一圈才发现是模式匹配对类型推断要求更严格。
  • 覆盖率插件的误报。我们用的JaCoCo覆盖率工具对于带when子句的switch分支识别不太准,会误报某些分支未覆盖。临时加了排除标记,等工具更新之后再去掉。
  • 同事的接受度。组里有两位用了五六年Java的同事,第一眼看到switch搭配箭头语法和when时觉得不像Java。我花了半个小时开了个小型分享会,带着把旧代码和新代码对比跑了一遍,他们当场就接受了,第二天开始在自己的模块里用上了。

少写了代码,多得了什么

代码行数减少只是一方面,更重要的收获在别处:

  • 类型安全被真正落实。密封接口保证所有子类型都被处理,不可能再出现“忘了处理某种订单”的bug。
  • 不可变性消除了副作用。Record让订单对象在整个处理链路中保持不变,不用再担心某个环节意外篡改了数据。
  • 新的业务分支加得快。增加新订单类型只需要新建一个Record实现密封接口,然后在switch里补一条分支,编译器会帮忙定位所有需要修改的地方。

什么时候可以开始用这套写法

模式匹配和Record在Java 21已经是正式特性,不是预览,生产环境可以直接上。密封类也是正式特性。唯一需要注意的是,如果你的项目还在跑Java 17甚至更早的版本,得先安排升级。不过从17升到21的改动不大,Spring Boot 3.2已经全面兼容,整体升级成本可控。

如果你的代码里也有大量instanceof和嵌套if-else在折磨人,建议挑一个相对独立的模块小范围试试。一旦习惯了模式匹配的写法,再看旧代码,会有种想去清理历史遗留的冲动——这种冲动,往往正是重构的最好动力。

Java 21模式匹配重构实战:把200行订单处理逻辑压缩到50行以内
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 java Java 21模式匹配重构实战:把200行订单处理逻辑压缩到50行以内 https://www.taomawang.com/server/java/2310.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务