告别"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 |
| 可测试性 | 差,一个方法测所有分支 | 好,每个子方法独立测试 |
| 可读性 | 需要跟踪嵌套逻辑 | 顺序阅读,一目了然 |
实操建议
- 先加测试再重构:模式匹配改变的是代码结构,不是逻辑,测试是安全网
- 优先用sealed class:编译器帮你检查穷举,少一个case直接编译报错
- guard条件别写太复杂 :
when后面超过2个条件就考虑提取方法 - Record优先:新代码用Record替代传统的DTO/VO,配合模式匹配效果最好
- 不要为了用而用:简单的if-else没必要硬改成switch,工具是解决问题的不是炫技的
写在最后
模式匹配不是什么"新潮语法糖",它是Java在向函数式编程范式靠拢的重要一步。用了之后你会发现,代码不只是变短了,而是变清晰了------你在表达"做什么",而不是"怎么做"。
9年Java开发,我越来越觉得:好代码不是写得巧妙,而是写得让人一看就懂。
📌 我是卷毛,9年Java开发,专注Java技术实战分享。
这篇文章如果对你有帮助,点个「在看」和「关注」,是我持续输出的最大动力。
下一篇我们聊虚拟线程的实战踩坑经验,关注不迷路!
《卷毛的技术笔记》------ 一起卷出技术力。 🔥