bigywater.tech
Java · 225

Switch 模式匹配:让分支覆盖范围可检查

switch 学会了按类型和 record 结构匹配。null 怎么处理?顺序和守卫怎么算?哪些写法会被编译器拒绝?这篇逐项真实编译、运行,并看字节码。

2 次
三个 when 守卫命中第二个后,守卫的求值次数
MatchException
record 访问器抛异常时的包装异常
typeSwitch
模式 switch 在字节码里的 invokedynamic 名称

null 的处理

实测 · 本机执行输出
  传统 switch(String) 遇到 null → NullPointerException
  模式 switch 没有 case null,遇到 null → NullPointerException
  写了 case null:→ case null 分支
  case null, default:→ null 与 default 合并
  Integer 42 经过三个 Integer 分支(大 / 中 / 小):→ 中
  对 Integer 的常量 switch,遇到 null → NullPointerException

顺序、守卫与按类型分派

实测 · 本机执行输出
  三个 when 守卫依次求值:结果 B,守卫被求值的次数 = 2(命中第二个后,第三个不再求值)
  守卫只对类型匹配的分支求值:结果 s2,求值次数 = 2(Integer 分支类型不符,守卫没有执行)
  按类型分派:Integer String Double Char List int[] null

record 模式与嵌套

实测 · 本机执行输出
  eval(Add(Num 2, Mul(Num 3, Num 4))) = 14;eval(Neg(Mul(Add(Num 1,Num 2), Num 5))) = -15
  record 模式里访问器抛出异常 → MatchException: java.lang.IllegalStateException: accessor boom;cause = java.lang.IllegalStateException: accessor boom
  嵌套 record 模式,Add(Num 1, null):→ 右边不是 Num 但是 Expr(右子模式 Expr y 能匹配 null)
  Add(Num 1, null) 对 (Num x, Num y)(null 不匹配类型模式 Num)再对 (var x, var y):→ B

编译期检查与字节码

实测 · 本机执行输出
  后面的分支被前面的类型模式支配 → this case label is dominated by a preceding case label
  带守卫的分支放在无守卫之前(合法) → 编译通过
  无守卫分支放在带守卫之前(被支配) → this case label is dominated by a preceding case label
  Object 上没有 default → the switch expression does not cover all possible input values
  sealed 缺一个子类 → the switch expression does not cover all possible input values
  sealed 全覆盖,没有 default → 编译通过;运行:2
  default 放在中间,后面还有类型分支 → this case label is dominated by a preceding case label
  分支里 fall-through(用 case ...: 旧语法穿透到有绑定变量的分支) → illegal fall-through to a pattern
  同一个 case 里 `case String s, Integer i`(多个模式) → illegal fall-through from a pattern
  字节码里与 typeSwitch 有关的行:
    #13 = InvokeDynamic      #0:#14         // #0:typeSwitch:(Ljava/lang/Object;I)I
    #14 = NameAndType        #15:#16        // typeSwitch:(Ljava/lang/Object;I)I
    #15 = Utf8               typeSwitch
  BootstrapMethods:
    BootstrapMethods
    #61 = MethodHandle       6:#62          // REF_invokeStatic java/lang/runtime/SwitchBootstraps.typeSwitch:(Ljava/lang/invoke/MethodHandles$Lookup;Ljav
    #62 = Methodref          #63.#64        // java/lang/runtime/SwitchBootstraps.typeSwitch:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Lja
    #63 = Class              #65            // java/lang/runtime/SwitchBootstraps
    #64 = NameAndType        #15:#66        // typeSwitch:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/la
    #65 = Utf8               java/lang/runtime/SwitchBootstraps

分步演示

Switch 模式匹配
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12(模式 switch 与 record 模式在 21 为正式特性)
    • 没有验证:与 if-instanceof 链的性能对比(没有测)、泛型 sealed 类型的穷尽性判断、枚举与 sealed 混合的 switch、unnamed pattern(_)在 JDK 21 是预览特性,没有使用、JDK 17 的预览版本差异
    京ICP备2026067502号-1京公网安备11010502063487号