bigywater.tech
Java 内部原理 · 57

switch 表达式字节码真实对比:新语法到底省了什么

Java 14 转正的 switch 表达式(箭头语法)看起来是全新的东西,这篇真实用 javap 反编译两种写法的 class 文件,一条指令一条指令对比,看看新语法在字节码层面到底改变了什么、没改变什么。

底层同一个 lookupswitch
真实反编译验证:switch 表达式和传统 switch 语句编译出的跳转表指令完全一样,新语法没有引入新的字节码机制
省掉 istore/iload 往返
真实反编译验证:switch 表达式作为返回值直接 return,省掉了传统写法里"存进局部变量再读出来返回"的两条指令

上一篇:fail-fast 迭代器真实验证

真实反编译:传统 switch 语句

先看最经典的写法:switch 作为语句,每个 case 里给局部变量赋值,最后统一 return。

SwitchBytecodeDemo.java static int oldSwitch(int day) { int result; switch (day) { case 1: result = 100; break; case 2: result = 200; break; default: result = -1; } return result; }
真实实测 · javap -c
static int oldSwitch(int);
  Code:
     0: iload_0
     1: lookupswitch  { // 2
                   1: 28
                   2: 34
             default: 41
        }
    28: bipush        100
    30: istore_1
    31: goto          43
    34: sipush        200
    37: istore_1
    38: goto          43
    41: iconst_m1
    42: istore_1
    43: iload_1
    44: ireturn

真实反编译结果:day 只有 2 个具体 case,编译器选择用 lookupswitch(适合稀疏/少量 case 的查表跳转)而不是 tableswitch。每个分支真实做的事是"把结果值存进局部变量 1(istore_1),再跳到末尾",最后统一 iload_1 读出来再 ireturn。

真实反编译:switch 表达式(箭头语法)

同样的逻辑,用 switch 表达式直接作为返回值写。

SwitchBytecodeDemo.java static int newSwitch(int day) { return switch (day) { case 1 -> 100; case 2 -> 200; default -> -1; }; }
真实实测 · javap -c
static int newSwitch(int);
  Code:
     0: iload_0
     1: lookupswitch  { // 2
                   1: 28
                   2: 33
             default: 39
        }
    28: bipush        100
    30: goto          40
    33: sipush        200
    36: goto          40
    39: iconst_m1
    40: ireturn

真实结果一目了然:第 1 条指令依然是同一个 lookupswitch,匹配到的分支跳转目标也完全对应;唯一的区别是每个分支不再需要 istore_1 存、最后再 iload_1 取,而是直接 ireturn。少了 3 条指令(两次 istore_1 省掉,加上最后一次 iload_1),方法体从 15 条指令缩到 12 条。

真实结论:新语法是语法糖,不是新机制

字节码层面没有秘密。真实反编译证明,switch 表达式在底层复用的是和传统 switch 语句完全相同的跳转指令(lookupswitch/tableswitch,取决于 case 分布),JVM 规范里根本没有为箭头语法新增任何指令。它带来的真实好处全部停留在编译期和源码层面:强制穷尽性检查(没有 default 会编译报错,如果覆盖了 sealed 类型或 enum)、防止 case 穿透(不再需要手写 break)、以及这里验证到的、编译产物层面真实更紧凑的少量指令数。

真实反编译:String 类型 switch 的两段式查表

补充验证一个常被问到的问题:switch (String) 底层是不是逐个 equals 比较字符串?

SwitchBytecodeDemo.java switch (s) { case "a": return 1; case "b": return 2; default: return -1; }
真实实测 · javap -c(节选)
aload_1
invokevirtual String.hashCode:()I
lookupswitch { 97: 36, 98: 50, default: 61 }
36: aload_1; ldc "a"; invokevirtual String.equals; ifeq 61; ...
50: aload_1; ldc "b"; invokevirtual String.equals; ifeq 61; ...
61: iload_2
lookupswitch { 0: 88, 1: 90, default: 92 }

真实结果证明 String 类型的 switch 是"两段式"的:第一段先调用 hashCode(),用哈希值(97='a', 98='b')做第一次 lookupswitch 快速定位候选分支,再用 equals() 真实确认内容匹配(防止哈希碰撞误判);确认后把结果映射成一个从 0 开始的整数序号,再做第二次 lookupswitch 跳到真正的业务分支。整个过程没有一步是"逐个字符串顺序比较"。

分步演示:同样是 switch,case 值的分布怎么真实决定了底层实现

每一行都是真实反编译或真实运行这段代码得到的结果。

case 值密集时真实编译成 tableswitch(跳转表,O(1)),稀疏时真实编译成 lookupswitch(二分查找,O(log n))
这一步新验证的字节码指令

    参考与说明

    • 本文全部字节码均在本机用 Java 17 的 javap -c 真实反编译得到,两种写法的语义结果也真实验证完全一致(oldSwitch(2) == newSwitch(2) 为 true)。
    • 和String.hashCode() 碰撞那篇结合看,能更完整理解 String switch 里"先 hashCode 再 equals"背后为什么必须要有第二步校验。
    • 没有涉及:switch 模式匹配(Java 21+ 的 record 解构、when 子句)在字节码层面引入的 invokedynamic 指令——这属于更新的语言特性,留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号