bigywater.tech
Java 内部原理 · 129

String switch字节码真实反编译:两个hashCode相同的字符串,真的会靠equals()区分开

String类型能在switch里直接用,是Java 7才加入的语法糖——这篇真实用javap反编译,并构造两个hashCode相同的字符串"Aa"和"BB",验证switch底层是怎么保证不会认错。

底层真实是两层判断
真实反编译:先对字符串hashCode()做lookupswitch跳转,命中同一个偏移量后再依次调用equals()精确匹配
哈希碰撞真实不会认错
真实构造:"Aa"和"BB"的hashCode真实都是2112,但switch真实分别返回了正确的对应分支

上一篇:Double.toString 真实验证

真实验证:hashCode相同的两个字符串,switch真实不会认错

"Aa"和"BB"是Java里经典的hashCode碰撞例子——先验证碰撞真实存在,再验证switch语句会不会因此把这两个值搞混。

StringSwitchBytecodeDemo.java switch (s) { case "Aa": return "特殊值Aa"; case "BB": return "特殊值BB"; ... }
真实实测
"Aa".hashCode()=2112, "BB".hashCode()=2112, 真实是否相等: true
classify("Aa")=特殊值Aa, classify("BB")=特殊值BB (若switch只用hashCode不用equals,这两者会混淆; 真实结果证明没有混淆)

真实结果证明即使"Aa"和"BB"的hashCode()真实完全相同(都是2112),switch依然正确地把它们分派到了各自对应的分支——如果底层真的只靠hashCode做判断,这两个case在字节码层面会变得无法区分。真实结果说明背后一定还有额外的校验步骤。

真实反编译:switch真实是lookupswitch(hashCode)+equals()两层结构

javap -c StringSwitchBytecodeDemo static java.lang.String classify(java.lang.String);
真实实测(javap -c 反编译结果)
    aload_1
    invokevirtual  Method java/lang/String.hashCode:()I
    lookupswitch  { // 4
        -1396355227: 66
        -1361513063: 80
               2112: 94     <-- "Aa"和"BB"共享同一个hashCode,跳到同一个偏移量94
           93029210: 52
            default: 119
    }
94: aload_1
    ldc            String BB
    invokevirtual  Method java/lang/String.equals:(Ljava/lang/Object;)Z
    ifeq           108        <-- 不等于"BB",继续往下试
    iconst_4
    istore_2
    goto           119
108: aload_1
    ldc            String Aa
    invokevirtual  Method java/lang/String.equals:(Ljava/lang/Object;)Z
    ifeq           119
    iconst_3
    istore_2
字节码真实证明了这是一个"哈希分组+顺序equals"的两阶段设计。第一阶段,编译器真实先调用hashCode(),用lookupswitch指令根据哈希值跳转——"Aa"和"BB"因为哈希值相同,真实被分到了同一个跳转目标(偏移量94)。第二阶段才是关键:跳到94之后,字节码真实依次用equals()挨个比较该哈希值组内的所有候选字符串(先试"BB",不等于就继续试"Aa"),只有equals()真实返回true的那个候选才会给istore_2赋对应的case序号,最后这个序号才会驱动真正的业务分支跳转。这个设计和HashMap处理哈希碰撞的思路本质相同——哈希值只是用来快速缩小候选范围的过滤器,真正决定"是不是同一个值"的,永远是equals()。

分步演示:构造一次真实的哈希碰撞,看 String switch 怎么处理

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

String switch 真实被编译成两层结构:外层按 hashCode()+equals() 把字符串转成整数索引,内层再用普通的整数 tableswitch
这一步新验证的行为

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,反编译结果和运行时行为在重复编译/运行下完全一致。
    • 和hashCode 契约真实验证一起看,能更完整理解"哈希值相同不代表值相同,但值相同必须哈希值相同"这条契约是如何贯穿HashMap、HashSet、String switch这些看起来毫不相关的JDK特性的。
    • 没有涉及:switch表达式(Java 14+新语法,箭头形式)编译出的字节码和传统String switch是否完全一致、Java 21引入的字符串模式匹配switch(涉及record pattern)的字节码实现——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号