Skip to content

Latest commit

 

History

History
99 lines (76 loc) · 5 KB

File metadata and controls

99 lines (76 loc) · 5 KB

switch 模式匹配与 record 解构

!!! info "版本信息" - 最低 JDK:21(switch 模式匹配 17–20 预览 → 21 正式;record 解构 19–20 预览 → 21 正式) - JEPJEP 441: Pattern Matching for switchJEP 440: Record Patterns

为什么需要

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):

--8<-- "examples/pattern-switch/src/main/java/com/javamodern/pmswitch/Shape.java"

然后是 examples/pattern-switch/真实可运行的 switch 逻辑(snippets 嵌入,与示例零漂移):

--8<-- "examples/pattern-switch/src/main/java/com/javamodern/pmswitch/PatternSwitchDemo.java"
  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 旧写法对比

=== "JDK 8(if-else + instanceof + 强转)" java 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("未知形状"); // 老是兜底,编译器帮不上忙 }

=== "JDK 21(switch 按类型 + record 解构)" java 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
Loading

常见坑 / 最佳实践

  • 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+强转+兜底的旧时代画上句号。

参考