Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
飞翔科技的支付模块出了个 Bug——孔蓝写的
switch语句根据支付类型分发处理,但每种类型都要先instanceof再做类型转换。他写了 80 多行的 switch 方法,里面嵌套了 5 个
instanceof:Object payment = ...; if (payment instanceof WechatPay w) { // 处理微信支付 } else if (payment instanceof AliPay a) { // 处理支付宝 } else if (payment instanceof UnionPay u) { // 处理银联 } else { throw new IllegalArgumentException("未知支付类型"); }白歌看了一眼:"这叠罗汉式的 if-else,以后用 JDK 17 的 switch 模式匹配,一行
case WechatPay w就能匹配类型并绑定变量。"孔蓝眼睛一亮:"switch 还能匹配类型?"
"JDK 17 预览,JDK 21 正式转正——" 白歌说,"配合密封类一起用,编译器还能帮你检查分支是否覆盖了所有子类型。"
定义表
| 概念 | 描述 |
|---|---|
| Pattern Matching for switch | JDK 17 预览特性(JDK 21 正式),将模式匹配扩展到 switch 语句和表达式,支持类型匹配、守卫模式、null 处理 |
| 类型模式(Type Pattern) | case ClassName variableName——匹配类型并将对象绑定到指定变量 |
| 守卫模式(Guarded Pattern / when 子句) | case ClassName variableName when condition——在类型匹配的基础上附加布尔条件 |
| 全类型覆盖(Exhaustiveness) | 与密封类配合时,编译器检查 switch 是否覆盖了所有可能的子类型 |
| null 处理 | case null -> ... 显式处理 null,避免 NullPointerException |
Mermaid 对比图:传统方式 vs Switch 模式匹配
基础用法:类型匹配
3.1 从 instanceof 链到 switch 模式匹配
// ===== 传统方式:instanceof + if-else 链 =====
static String formatOld(Object obj) {
if (obj instanceof Integer i) {
return "整数: " + i;
} else if (obj instanceof Long l) {
return "长整数: " + l;
} else if (obj instanceof Double d) {
return "浮点数: " + d;
} else if (obj instanceof String s) {
return "字符串: '" + s + "'";
} else {
return "未知类型: " + obj.getClass().getName();
}
}
// ===== JDK 17 预览:switch 模式匹配 =====
static String formatNew(Object obj) {
return switch (obj) {
case Integer i -> "整数: " + i;
case Long l -> "长整数: " + l;
case Double d -> "浮点数: " + d;
case String s -> "字符串: '" + s + "'";
case null -> "null 值";
default -> "未知类型: " + obj.getClass().getName();
};
}
输出:
整数: 42
长整数: 9999999999
浮点数: 3.14159
字符串: '飞翔科技'
null 值
未知类型: java.util.Date
3.2 与传统 switch 的区别
// ===== 传统 switch(JDK 14+ 箭头语法)=====
// 只能匹配 byte / short / char / int / String / enum
String result = switch (day) {
case MONDAY -> "周一";
case FRIDAY -> "周五";
default -> "其他";
};
// ===== 模式匹配 switch(JDK 17 预览)=====
// 可以匹配任意 Object 类型!
String result = switch (obj) {
case Integer i -> "整数 " + i;
case String s -> "字符串 " + s;
case null -> "空值";
default -> "其他类型";
};
守卫模式 — when 子句
when 子句在类型匹配成功后,追加一个布尔条件做进一步筛选:
// ===== 支付金额分级处理 =====
sealed interface Payment permits WechatPay, AliPay, UnionPay {}
record WechatPay(double amount, String openId) implements Payment {}
record AliPay(double amount, String account) implements Payment {}
record UnionPay(double amount, String cardNo) implements Payment {}
public class GuardedPatternDemo {
static String processPayment(Payment payment) {
return switch (payment) {
// 小额支付 —— 免密
case WechatPay w when w.amount() < 500
-> "微信小额支付(免密): " + w.amount() + "元";
// 大额支付 —— 需要密码
case WechatPay w when w.amount() >= 500
-> "微信大额支付(需密码): " + w.amount() + "元";
// 支付宝同样分级
case AliPay a when a.amount() < 1000
-> "支付宝小额支付: " + a.amount() + "元";
case AliPay a
-> "支付宝大额支付: " + a.amount() + "元";
// 银联统一处理
case UnionPay u
-> "银联支付: " + u.amount() + "元,卡号尾号 "
+ u.cardNo().substring(u.cardNo().length() - 4);
};
}
public static void main(String[] args) {
System.out.println(processPayment(new WechatPay(200, "openid_001")));
System.out.println(processPayment(new WechatPay(2000, "openid_002")));
System.out.println(processPayment(new AliPay(3000, "ali@feixiang.com")));
System.out.println(processPayment(new UnionPay(500, "6222000012345678")));
}
}
输出:
微信小额支付(免密): 200.0元
微信大额支付(需密码): 2000.0元
支付宝大额支付: 3000.0元
银联支付: 500.0元,卡号尾号 5678
注意:
when是 JDK 19 预览中引入的关键字,JDK 17 预览版使用&&语法。此处以 JDK 21 最终语法展示。
null 处理
传统 switch 遇到 null 会直接抛 NullPointerException。模式匹配 switch 允许显式 case null:
static String handleWithNull(Object obj) {
return switch (obj) {
case null -> "对象为 null(正常处理,不抛异常)";
case String s -> "字符串: " + s;
case Integer i -> "整数: " + i;
default -> "其他类型";
};
}
public static void main(String[] args) {
System.out.println(handleWithNull(null)); // 不抛异常
System.out.println(handleWithNull("飞翔科技"));
System.out.println(handleWithNull(42));
}
输出:
对象为 null(正常处理,不抛异常)
字符串: 飞翔科技
整数: 42
与密封类的配合:完整性检查
密封类 + Switch 模式匹配 = 编译器自动验证全覆盖:
// ===== 密封类定义 =====
sealed interface HttpStatus permits Success, Redirect, ClientError, ServerError {}
record Success(int code, String body) implements HttpStatus {}
record Redirect(int code, String location) implements HttpStatus {}
record ClientError(int code, String message) implements HttpStatus {}
record ServerError(int code, String message) implements HttpStatus {}
// ===== Switch 模式匹配(编译器检查完整性)=====
static String handle(HttpStatus status) {
return switch (status) {
case Success s -> "成功 " + s.code() + ": " + s.body();
case Redirect r -> "重定向 " + r.code() + " -> " + r.location();
case ClientError ce -> "客户端错误 " + ce.code() + ": " + ce.message();
case ServerError se -> "服务端错误 " + se.code() + ": " + se.message();
// 编译器知道只有这 4 种子类型,不需要 default!
};
}
如果注释掉任意一个 case——编译器会报错:
the switch statement does not cover all possible input values。
代码对比总结
| 维度 | 传统 instanceof 链 | Switch 模式匹配 |
|---|---|---|
| 代码行数 | 每种类型 3 行(判断 + 强转 + 使用) | 每种类型 1 行 |
| 类型安全 | 需手动强转 | 自动绑定,无强转 |
| null 处理 | 手动判空 | case null -> 一行 |
| 守卫条件 | 嵌套 if | when 子句内联 |
| 完整性检查 | 无(密封类场景需手动 default) | 密封类场景编译器自动检查 |
| 可读性 | if-else 叠罗汉 | switch 结构清晰扁平 |
JDK 版本演进
| JDK 版本 | 状态 | 关键变化 |
|---|---|---|
| JDK 17 | 预览(JEP 406) | 首次引入 switch 模式匹配 |
| JDK 18 | 二次预览(JEP 420) | 改进类型推断 |
| JDK 19 | 三次预览(JEP 427) | 引入 when 子句替代 && |
| JDK 20 | 四次预览(JEP 433) | 改进 exhaustiveness 检查 |
| JDK 21 | 正式特性(JEP 441) | 所有语法稳定,正式转正 |
当前使用建议:生产项目建议使用 JDK 21+ 的正式版本。JDK 17 环境下需要用
--enable-preview启用。
总结
Switch 模式匹配是 Java 语言在模式匹配道路上的重要里程碑。与记录类、密封类共同构成了"代数数据类型(ADT)+ 模式匹配"的现代编程范式,彻底告别了 instanceof + 强制转换 + if-else 链的冗长代码。
核心三要素:类型匹配(case Type var)→ 守卫条件(when)→ 完整性检查(密封类配合),三者合力使 switch 从简单的值匹配进化为强大的类型分派引擎。