Java 模式匹配 switch 怎么用?
Java 模式匹配 switch 自 JDK 21 正式定型,JDK 17–20 需加 --enable-preview;用 case 类型 变量名 -> 配合 when 守卫、case null 和密封类型穷尽检查,可替代 instanc
结论:Java 的模式匹配 switch 从 JDK 21(2023 年 9 月发布,JEP 441)起正式定型,JDK 17/18/19/20 只能加 --enable-preview 预览使用。核心用法是 case 类型 变量名 ->,配合 when 守卫、case null、密封类型的穷尽性检查,基本可以替代一长串 if (x instanceof ...) 的写法。
switch 模式匹配从哪个版本开始能用?
正式可用是 JDK 21。用 JDK 21 时直接写就行,编译命令是 javac --release 21 Demo.java;如果只有 JDK 17 到 20,需要额外加 javac --enable-preview --release 17 Demo.java,运行时也要 java --enable-preview Demo,否则报 Preview features are not enabled。
语法演化路径是:JEP 406(JDK 17 首次预览)→ JEP 420 → JEP 427 → JEP 433 → JEP 441(JDK 21 转正)。配套的记录模式解构(case Point(int x, int y))由 JEP 440 在同版本转正,两者经常一起用。
基本语法怎么写?
结论:模式 switch 用箭头 -> 写分支,每个 case 后跟类型模式或常量,自上而下匹配,命中一个就停。
static String describe(Object obj) {
return switch (obj) {
case Integer i when i > 100 -> "大于 100 的整数: " + i;
case Integer i -> "整数: " + i;
case String s -> "字符串: " + s;
case null -> "null";
default -> "其它类型";
};
}
三条硬规则:第一,绑定变量 i、s 的作用域只在箭头右侧,出不了这个分支;第二,类型模式和常量模式可以混着写,case Integer i 和 case 0 能共存;第三,顺序敏感,子类型必须写在父类型前面,把 case Object o 放在 case String s 前面会直接编译失败,报错信息是 this case label is dominated by a preceding case label。
什么时候可以不写 default?
结论:当 switch 的选择器是密封类/密封接口,且所有 permits 子类型都被覆盖时,编译器认定已经穷尽,可以不写 default;enum 覆盖全部常量时同理。其它类型(比如 Object)必须写 default,否则报 the switch expression does not cover all possible input values。
注意:使用了类型模式的 switch 语句(不只是表达式)同样要求穷尽。这点和传统的 switch (int) 不一样,老写法可以只写几个分支,新模式写不全直接编译不过。
sealed interface Shape permits Circle, Rect {}
record Circle(double r) implements Shape {}
record Rect(double w, double h) implements Shape {}
static double area(Shape s) {
return switch (s) {
case Circle(double r) -> Math.PI * r * r;
case Rect(double w, double h) -> w * h;
}; // 无需 default
}
case null 有什么用?
结论:传统 switch 传 null 直接抛 NullPointerException,模式 switch 可以用 case null -> 显式接住它。如果想让 null 走默认逻辑,可以合并写成 case null, default -> ...。
要留意:穷尽性检查不包含 null。一个密封类型的 switch 就算覆盖了全部子类,运行时传入 null 仍然抛 NPE——所以生产代码里建议显式加一个 case null。
when 守卫怎么写?
结论:在模式后面加 when 布尔表达式 做二次筛选,只有条件为 true 才走该分支。
when 是 JDK 21 新增的上下文关键字(JEP 441 前用的是 && 和 case ... &&,预览期改过,正式版统一为 when)。守卫里的表达式可以引用刚绑定的变量,比如 case String s when s.isEmpty() -> "空串"。守卫不改变穷尽性判断的复杂度,写多了也不会影响编译器静态检查。
记录模式怎么解构?
结论:case Point(int x, int y) 直接把 record 的组件拆成局部变量,支持任意层嵌套,无需再调 x()、y()。
record Point(int x, int y) {}
record Line(Point start, Point end) {}
static String fmt(Object o) {
return switch (o) {
case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
"从 (%d,%d) 到 (%d,%d)".formatted(x1, y1, x2, y2);
case Point(int x, int y) -> "点 (%d,%d)".formatted(x, y);
default -> "未知";
};
}
有哪些坑要注意?
第一,泛型擦除:case List<String> l 编译不过,只能写 case List<?> l,再靠守卫判断元素类型。第二,同一个类型的模式不能重复出现,编译器会报 dominated 错误。第三,升级到 JDK 21 后,老代码里如果有变量名叫 when,在 case 标签位置可能被当成关键字解析,改个名即可。第四,预览期语法变动过,网上 JDK 17 时代带 && 的示例在 JDK 21 上编译失败,以官方最终语法为准。
一句话收束:JDK 21 起,case 类型 变量 -> + when 守卫 + case null + 密封类穷尽性,就是模式匹配 switch 的标准写法;JDK 17 到 20 记得开 --enable-preview,同时避开泛型擦除和支配顺序这两个编译期陷阱。
转载请注明出处,版权归原作者所有。