告别"if-else地狱"!Java 21模式匹配,代码优雅了10倍

告别"if-else地狱"!Java 21模式匹配,代码优雅了10倍

如果你打开一个方法,看到的是50行if-else嵌套,你会是什么感受?反正我是想砸键盘。

大家好,我是卷毛。

前段时间接手了一个老项目,有个订单处理方法,if-else嵌套了7层,478行代码。我花了3天重构完,用的是什么?Java 21的模式匹配

今天这篇文章,我把自己实际用到的5种重构手法分享出来,每种都有真实代码对比,保证你看完就能用。


先看个"恐怖故事"

这是真实代码,我只改了变量名:

java 复制代码
// 重构前 ------ 478行的怪物
public void processOrder(Order order) {
    if (order != null) {
        if (order.getType() == OrderType.NORMAL) {
            if (order.getStatus() == OrderStatus.PENDING) {
                if (order.getAmount() > 1000) {
                    if (order.getUser().getLevel() == UserLevel.VIP) {
                        // VIP大额待处理订单
                        if (order.getPaymentMethod() == PaymentMethod.WECHAT) {
                            vipWechatLargeOrderProcess(order);
                        } else if (order.getPaymentMethod() == PaymentMethod.ALIPAY) {
                            vipAlipayLargeOrderProcess(order);
                        } else {
                            vipOtherLargeOrderProcess(order);
                        }
                    } else {
                        // 普通用户大额待处理订单
                        normalLargeOrderProcess(order);
                    }
                } else {
                    // 小额订单处理...
                    // 还有3层嵌套...
                }
            } else if (order.getStatus() == OrderStatus.PAID) {
                // 已支付订单处理...
            } else if (order.getStatus() == OrderStatus.CANCELLED) {
                // 取消订单处理...
            }
        } else if (order.getType() == OrderType.GROUP) {
            // 团购订单...
            // 又是几层嵌套...
        }
    }
}

看完是不是血压上来了?别急,我们一步步来。


手法一:instanceof模式匹配 ------ 干掉强转

痛点instanceof判断完还要强转,啰嗦。

java 复制代码
// ❌ 以前
if (obj instanceof String) {
    String str = (String) obj; // 多余的强转
    System.out.println(str.length());
}

// ✅ Java 21
if (obj instanceof String str) {
    System.out.println(str.length());
}

实战场景

java 复制代码
// 处理不同类型的消息体
public void handleMessage(Object message) {
    if (message instanceof TextMessage tm) {
        textProcessor.process(tm.getContent());
    } else if (message instanceof ImageMessage im) {
        imageProcessor.process(im.getUrl(), im.getWidth(), im.getHeight());
    } else if (message instanceof VideoMessage vm) {
        videoProcessor.process(vm.getUrl(), vm.getDuration());
    } else if (message instanceof null) {
        log.warn("收到空消息");
    }
}

一行代码省掉了声明+强转,10个分支就省10行。


手法二:switch模式匹配 ------ 替代if-else链

痛点:基于类型的分支判断,if-else写起来又长又丑。

java 复制代码
// ❌ 以前 ------ 类型判断+强转,又臭又长
public double calculateArea(Shape shape) {
    if (shape instanceof Circle) {
        Circle c = (Circle) shape;
        return Math.PI * c.getRadius() * c.getRadius();
    } else if (shape instanceof Rectangle) {
        Rectangle r = (Rectangle) shape;
        return r.getWidth() * r.getHeight();
    } else if (shape instanceof Triangle) {
        Triangle t = (Triangle) shape;
        double s = (t.getA() + t.getB() + t.getC()) / 2;
        return Math.sqrt(s * (s - t.getA()) * (s - t.getB()) * (s - t.getC()));
    } else {
        throw new IllegalArgumentException("未知形状: " + shape.getClass().getName());
    }
}

// ✅ Java 21 ------ switch模式匹配
public double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t -> {
            double s = (t.a() + t.b() + t.c()) / 2;
            yield Math.sqrt(s * (s - t.a()) * (s - t.b()) * (s - t.c()));
        }
        case null -> throw new IllegalArgumentException("形状不能为null");
        default -> throw new IllegalArgumentException("未知形状");
    };
}

注意:switch模式匹配要求穷举所有可能,如果你用了sealed class,编译器会帮你检查。


手法三:Record + 模式匹配 ------ 替代DTO地狱

痛点:处理不同类型的消息/事件,DTO类一堆,处理逻辑if-else一堆。

先定义sealed接口和Record:

java 复制代码
// 定义一个密封接口,限定实现类型
public sealed interface PaymentEvent permits PaymentSuccess, PaymentFailed, RefundProcessed {}

public record PaymentSuccess(String orderId, BigDecimal amount, String tradeNo) implements PaymentEvent {}
public record PaymentFailed(String orderId, String reason, int retryCount) implements PaymentEvent {}
public record RefundProcessed(String orderId, BigDecimal refundAmount, String refundNo) implements PaymentEvent {}

处理逻辑:

java 复制代码
// 简洁、类型安全、编译器保证穷举
public void handlePaymentEvent(PaymentEvent event) {
    switch (event) {
        case PaymentSuccess(String orderId, BigDecimal amount, String tradeNo) ->
            orderService.confirmPayment(orderId, amount, tradeNo);

        case PaymentFailed(String orderId, String reason, int retryCount) when retryCount < 3 ->
            retryService.scheduleRetry(orderId, retryCount + 1);

        case PaymentFailed(String orderId, String reason, int retryCount) ->
            notifyService.notifyFailure(orderId, reason);

        case RefundProcessed(String orderId, BigDecimal refundAmount, String refundNo) ->
            refundService.completeRefund(orderId, refundAmount, refundNo);
    }
}

注意看 when retryCount < 3 这个guard条件------以前要用if嵌套实现的逻辑,现在一行搞定。

Record解构的模式匹配对比

java 复制代码
// Record组件可以直接解构
case PaymentSuccess(String orderId, BigDecimal amount, String tradeNo)

// 也可以只绑定部分组件,用 _ 忽略不关心的
case PaymentSuccess(String orderId, _, _)

// 甚至全忽略
case PaymentSuccess(_, _, _)

手法四:null安全模式匹配 ------ 告别NPE恐惧

痛点:null检查散落各处,漏了一个就NPE。

java 复制代码
// ❌ 以前 ------ 各种null检查
public String getUserName(User user) {
    if (user == null) {
        return "匿名用户";
    }
    if (user.getProfile() == null) {
        return user.getName();
    }
    if (user.getProfile().getNickname() == null) {
        return user.getName();
    }
    return user.getProfile().getNickname();
}

// ✅ Java 21 ------ null模式匹配
public String getUserName(User user) {
    return switch (user) {
        case null -> "匿名用户";
        case User u when u.profile() == null -> u.name();
        case User u when u.profile().nickname() == null -> u.name();
        case User u -> u.profile().nickname();
    };
}

配合Optional更优雅:

java 复制代码
public String getUserName(User user) {
    return Optional.ofNullable(user)
        .map(User::getProfile)
        .map(Profile::getNickname)
        .orElseGet(() -> Optional.ofNullable(user)
            .map(User::getName)
            .orElse("匿名用户"));
}

手法五:重构开头的"恐怖故事"

用上面4种手法组合,478行变62行:

java 复制代码
// 重构后 ------ 62行,清晰可维护
public void processOrder(Order order) {
    switch (order) {
        case null -> log.warn("收到空订单");

        case Order o when o.type() == OrderType.NORMAL ->
            processNormalOrder(o);

        case Order o when o.type() == OrderType.GROUP ->
            processGroupOrder(o);
    }
}

private void processNormalOrder(Order order) {
    switch (order) {
        case Order(var id, _, var amount, var status, var user, var payment) 
            when status == OrderStatus.PENDING && amount.compareTo(BigDecimal.valueOf(1000)) > 0
                && user.level() == UserLevel.VIP ->
            processVipLargeOrder(order, payment);

        case Order(_, _, var amount, var status, var user, _) 
            when status == OrderStatus.PENDING && amount.compareTo(BigDecimal.valueOf(1000)) > 0 ->
            normalLargeOrderProcess(order);

        case Order(_, _, _, var status, _, _) when status == OrderStatus.PAID ->
            paidOrderProcess(order);

        case Order(_, _, _, var status, _, _) when status == OrderStatus.CANCELLED ->
            cancelledOrderProcess(order);

        default -> pendingSmallOrderProcess(order);
    }
}

private void processVipLargeOrder(Order order, PaymentMethod payment) {
    switch (payment) {
        case WECHAT -> vipWechatLargeOrderProcess(order);
        case ALIPAY -> vipAlipayLargeOrderProcess(order);
        default -> vipOtherLargeOrderProcess(order);
    }
}

478行 → 62行,而且每一行都在说"做什么",而不是"怎么判断"。


重构前后对比

维度 重构前 重构后
代码行数 478行 62行
最大嵌套层级 7层 2层
圈复杂度 34 8
新增分支成本 在大方法里加if-else 加一个case
可测试性 差,一个方法测所有分支 好,每个子方法独立测试
可读性 需要跟踪嵌套逻辑 顺序阅读,一目了然

实操建议

  1. 先加测试再重构:模式匹配改变的是代码结构,不是逻辑,测试是安全网
  2. 优先用sealed class:编译器帮你检查穷举,少一个case直接编译报错
  3. guard条件别写太复杂when后面超过2个条件就考虑提取方法
  4. Record优先:新代码用Record替代传统的DTO/VO,配合模式匹配效果最好
  5. 不要为了用而用:简单的if-else没必要硬改成switch,工具是解决问题的不是炫技的

写在最后

模式匹配不是什么"新潮语法糖",它是Java在向函数式编程范式靠拢的重要一步。用了之后你会发现,代码不只是变短了,而是变清晰了------你在表达"做什么",而不是"怎么做"。

9年Java开发,我越来越觉得:好代码不是写得巧妙,而是写得让人一看就懂。


📌 我是卷毛,9年Java开发,专注Java技术实战分享。

这篇文章如果对你有帮助,点个「在看」和「关注」,是我持续输出的最大动力。

下一篇我们聊虚拟线程的实战踩坑经验,关注不迷路!

《卷毛的技术笔记》------ 一起卷出技术力。 🔥

相关推荐
Yeats_Liao1 小时前
14:Servlet中的页面跳转-Java Web
java·后端·架构
武子康1 小时前
调查研究-201 Rust 里的 dev build 和 release build:为什么同一份代码性能差这么多?
后端·架构·rust
鹤望兰6751 小时前
字节跳动国际支付-后端开发-三面面经
java
石小石Orz1 小时前
AI具身交互:实现一个会说话的3D虚拟伴侣
前端·人工智能·后端
Ai拆代码的曹操1 小时前
容器 CPU Throttling 有多坑?K8s CFS 限制让 P99 慢了 16 倍
后端
夕阳与风馨1 小时前
大文件(20GB+)SFTP 下载模块设计与实现
后端·架构
Flittly1 小时前
【AgentScope Java新手村系列】(14)人机交互
java·spring boot·spring
Dilee2 小时前
Spring AI 2.0.0 接 Skill 最小 Demo:SkillsTool 加载 SKILL.md 一次跑通
后端
zoulee242 小时前
doris-python:让 SQLAlchemy 玩转 Apache Doris 多驱动生态
后端