跳转至

switch 模式匹配与 record 解构#

版本信息

为什么需要#

switch 表达式(JDK 14)只能按常量分支。要在 switch 里「按对象类型」分发,JDK 17 之前只能堆 if-else + instanceof。JEP 441 把模式匹配搬进 switch:可以 case Integer icase 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"));
    }
}
  1. :material-lightbulb: area 按 record 类型匹配并解构组件;Shape 是 sealed,三个实现者全覆盖 → 无需 defaultclassify 展示 case nullwhen 守卫。

运行 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 绑给 rr 在箭头右侧可直接用。
  • 穷尽性(exhaustiveness):switch 表达式必须覆盖所有可能值。Shape 是 sealed,编译器知道实现者恰好是 Circle/Square/Rectangle,三者齐全即穷尽;漏一个会编译报错(不是运行时兜底)。非 sealed/开放的类型则需 default
  • case nullwhen 守卫:JEP 441 允许显式 case null(旧 switch 对 null 直接 NPE);case Integer i when i < 0when 在模式之上加布尔条件,多个同类型分支按顺序匹配。
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 不是默认:旧 switchnull 直接抛 NPE;新模式下,不写 case nullnull 仍会走 default 或抛 NPE——要显式 case null 才优雅处理。
  • instanceof 模式匹配(JDK 16)是递进关系:单分支用 instanceof,多分支用 switch 模式匹配;record 解构让「拆字段」也一步到位。

小结#

switch 模式匹配 + record 解构把「按类型分发、按结构取值」两件事在 switch 里一次完成。与 sealed 的穷尽性检查合在一起,JDK 21 终于让 Java 拥有了顺手的代数数据类型写法——if-else+强转+兜底的旧时代画上句号。

参考#