switch 模式匹配与 record 解构#
版本信息
- 最低 JDK:21(switch 模式匹配 17–20 预览 → 21 正式;record 解构 19–20 预览 → 21 正式)
- JEP:JEP 441: Pattern Matching for switch | JEP 440: Record Patterns
为什么需要#
switch 表达式(JDK 14)只能按常量分支。要在 switch 里「按对象类型」分发,JDK 17 之前只能堆 if-else + instanceof。JEP 441 把模式匹配搬进 switch:可以 case Integer i、case String s,还能 case null、用 when 守卫细化。JEP 440 再补上 record 解构:case Circle(double r) 把记录的组件直接拆出来。配合 sealed,编译器能判定穷尽性——分支覆盖所有可能时无需 default。
语法#
先把模型建好——一个 sealed 接口 + 三个 record(代数数据类型的三件套:sealed + record + pattern matching):
package com.javamodern.pmswitch;
// sealed 限定允许的实现者(JEP 409),编译器由此能判定 switch 穷尽性(JEP 441)
public sealed interface Shape permits Circle, Square, Rectangle {
}
然后是 examples/pattern-switch/ 里真实可运行的 switch 逻辑(snippets 嵌入,与示例零漂移):
package com.javamodern.pmswitch;
// JEP 441(switch 模式匹配)+ JEP 440(record 解构):
// sealed 让编译器知道所有实现者 → switch 可穷尽、无需 default;
// record 让分支「按类型 + 组件」一次解构。
public class PatternSwitchDemo {
public static double area(Shape shape) {
return switch (shape) { // (1) 按类型匹配 + record 解构
case Circle(double r) -> Math.PI * r * r; // 解构出 radius
case Square(double s) -> s * s;
case Rectangle(double w, double h) -> w * h; // 一次解构两个组件
}; // sealed permits 已穷尽,无需 default
}
public static String classify(Object obj) {
// switch 也能匹配 null(JEP 441)、用 when 守卫细化分支
return switch (obj) {
case null -> "空";
case Integer i when i < 0 -> "负整数";
case Integer i -> "整数 " + i;
case String s -> "字符串 " + s;
default -> "其它";
};
}
public static void main(String[] args) {
System.out.println("area(Circle(2)) = " + area(new Circle(2)));
System.out.println("area(Square(3)) = " + area(new Square(3)));
System.out.println("area(Rectangle(3,4)) = " + area(new Rectangle(3, 4)));
System.out.println(classify(null));
System.out.println(classify(-5));
System.out.println(classify(7));
System.out.println(classify("hi"));
}
}
- :material-lightbulb:
area按 record 类型匹配并解构组件;Shape是 sealed,三个实现者全覆盖 → 无需default。classify展示case null与when守卫。
运行 PatternSwitchDemo.main 的实测输出(JDK 21):
area(Circle(2)) = 12.566370614359172
area(Square(3)) = 9.0
area(Rectangle(3,4)) = 12.0
空
负整数
整数 7
字符串 hi
与 JDK 8 旧写法对比#
if (shape instanceof Circle) {
double r = ((Circle) shape).radius();
return Math.PI * r * r;
} else if (shape instanceof Square) {
double s = ((Square) shape).side();
return s * s;
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return r.width() * r.height();
} else {
throw new IllegalArgumentException("未知形状"); // 老是兜底,编译器帮不上忙
}
return switch (shape) { // sealed 穷尽,无 default
case Circle(double r) -> Math.PI * r * r;
case Square(double s) -> s * s;
case Rectangle(double w, double h) -> w * h;
};
底层原理#
- 类型模式 + 解构:
case Circle(double r)是「类型模式 + record 模式」的嵌套——先匹配类型,再把组件radius绑给r,r在箭头右侧可直接用。 - 穷尽性(exhaustiveness):switch 表达式必须覆盖所有可能值。
Shape是 sealed,编译器知道实现者恰好是Circle/Square/Rectangle,三者齐全即穷尽;漏一个会编译报错(不是运行时兜底)。非 sealed/开放的类型则需default。 case null与when守卫:JEP 441 允许显式case null(旧 switch 对null直接 NPE);case Integer i when i < 0用when在模式之上加布尔条件,多个同类型分支按顺序匹配。
graph TD
S["switch (shape)"] --> C["case Circle(double r)"]
S --> Q["case Square(double s)"]
S --> R["case Rectangle(double w, h)"]
E["sealed permits Circle,Square,Rectangle"] -.编译器据此判穷尽.- S
X["漏写任一分支"] -.编译报错.- S
style S fill:#bbdefb
style X fill:#ffcdd2
style E fill:#c8e6c9
常见坑 / 最佳实践#
when守卫要按顺序:case Integer i when i < 0必须在case Integer i之前,否则永不被命中(无守卫的分支先吃掉)。- 非 sealed 类型仍要
default:只有 switch 的选择器类型被sealed限定、或被 record 全覆盖时才能省default;普通Object必须有default(如本例classify)。 case null不是默认:旧switch遇null直接抛 NPE;新模式下,不写case null时null仍会走default或抛 NPE——要显式case null才优雅处理。- 与
instanceof模式匹配(JDK 16)是递进关系:单分支用instanceof,多分支用switch模式匹配;record 解构让「拆字段」也一步到位。
小结#
switch 模式匹配 + record 解构把「按类型分发、按结构取值」两件事在 switch 里一次完成。与 sealed 的穷尽性检查合在一起,JDK 21 终于让 Java 拥有了顺手的代数数据类型写法——if-else+强转+兜底的旧时代画上句号。