bigywater.tech
Java · 338

枚举 switch 的兼容性

库升级时给枚举加了个常量,已经编译好的客户端会怎样?switch 是按序号还是按名字?删掉常量会炸吗?这篇用 6 个版本的枚举 jar 和同一个客户端 class 逐个验证。

MatchException
没有 default 的 switch 表达式遇到新增枚举常量时抛出的异常
不失败
库删除了客户端 switch 里用到的枚举常量 GREEN 时,客户端的行为
ordinal()
直接用序号的写法在中间插入新常量后会变化;switch 本身不受影响

客户端的字节码

实测 · 本机执行输出
  类文件: ['Client$1.class', 'Client.class']
  Client$1 的字段: ['static final int[] $SwitchMap$Color;', '6: putstatic #7 // Field $SwitchMap$Color:[I', '9: getstatic #7 // Field $SwitchMap$Color:[I']
  Client$1 静态初始化里的关键指令(前 10 条):
    0: invokestatic #1 // Method Color.values:()[LColor;
    6: putstatic #7 // Field $SwitchMap$Color:[I
    9: getstatic #7 // Field $SwitchMap$Color:[I
    12: getstatic #13 // Field Color.RED:LColor;
    15: invokevirtual #17 // Method Color.ordinal:()I
    18: iconst_1
    19: iastore
    23: astore_0
    24: getstatic #7 // Field $SwitchMap$Color:[I
    27: getstatic #23 // Field Color.GREEN:LColor;
  异常表条目数(每个 case 常量一个 try/catch NoSuchFieldError): 2
  exprExhaustive(没有 default 的 switch 表达式)的字节码关键行:
    0: getstatic #9 // Field Client$1.$SwitchMap$Color:[I
    4: invokevirtual #15 // Method Color.ordinal:()I
    7: iaload
    8: lookupswitch { // 2
    36: new #29 // class java/lang/MatchException
    42: invokespecial #31 // Method java/lang/MatchException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
    45: athrow

不重新编译换版本运行

实测 · 本机执行输出
  [v1]
  枚举现在的常量:RED(0) GREEN(1)
    stmtDefault      RED→红  GREEN→绿
    stmtNoDefault    RED→红  GREEN→绿
    exprExhaustive   RED→红  GREEN→绿
    exprDefault      RED→红  GREEN→绿
    ordinalSwitch    RED→ordinal 0  GREEN→ordinal 1
  [v2-新增在末尾]
  枚举现在的常量:RED(0) GREEN(1) BLUE(2)
    stmtDefault      RED→红  GREEN→绿  BLUE→其它
    stmtNoDefault    RED→红  GREEN→绿  BLUE→未匹配
    exprExhaustive   RED→红  GREEN→绿  BLUE→MatchException
    exprDefault      RED→红  GREEN→绿  BLUE→其它
    ordinalSwitch    RED→ordinal 0  GREEN→ordinal 1  BLUE→ordinal 其它
  [v3-新增在中间]
  枚举现在的常量:RED(0) BLUE(1) GREEN(2)
    stmtDefault      RED→红  BLUE→其它  GREEN→绿
    stmtNoDefault    RED→红  BLUE→未匹配  GREEN→绿
    exprExhaustive   RED→红  BLUE→MatchException  GREEN→绿
    exprDefault      RED→红  BLUE→其它  GREEN→绿
    ordinalSwitch    RED→ordinal 0  BLUE→ordinal 1  GREEN→ordinal 其它
  [v4-重排]
  枚举现在的常量:GREEN(0) RED(1)
    stmtDefault      GREEN→绿  RED→红
    stmtNoDefault    GREEN→绿  RED→红
    exprExhaustive   GREEN→绿  RED→红
    exprDefault      GREEN→绿  RED→红
    ordinalSwitch    GREEN→ordinal 0  RED→ordinal 1
  [v5-删除 GREEN]
  枚举现在的常量:RED(0)
    stmtDefault      RED→红
    stmtNoDefault    RED→红
    exprExhaustive   RED→红
    exprDefault      RED→红
    ordinalSwitch    RED→ordinal 0
  [v6-重命名 GREEN→LIME]
  枚举现在的常量:RED(0) LIME(1)
    stmtDefault      RED→红  LIME→其它
    stmtNoDefault    RED→红  LIME→未匹配
    exprExhaustive   RED→红  LIME→MatchException
    exprDefault      RED→红  LIME→其它
    ordinalSwitch    RED→ordinal 0  LIME→ordinal 1

分步演示

枚举 switch 兼容性
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;一个枚举 Color 的 6 个版本与同一个客户端 class 文件;客户端用 switch 语句 / 表达式 / ordinal() 五种写法
    • 没有验证:模式匹配 switch(带 case Color c)与 SwitchBootstraps.enumSwitch 的行为(本例没有用到)、枚举在不同类加载器里的映射表、序列化枚举常量被删除时的行为、Kotlin when 对枚举的编译方式、javac 在枚举与 switch 同一编译单元里不生成 $SwitchMap 的情形(见枚举初始化一文)
    京ICP备2026067502号-1京公网安备11010502063487号