Java switch实现原理透彻理解

Java 官方文档描述:

A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types, the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and Integer

在基本数据类型 byte / char 下

public class SwitchBasicTest {
    
    private int switchByte(byte i) {
        int ret;
        switch (i) {
            case 0:
                ret = 10;
                break;
            case 1:
                ret = 11;
                break;
            default:
                ret = 0;
        }

        return ret;
    }
    private int switchChar(char i) {
        int ret;
        switch (i) {
            case 0:
                ret = 10;
                break;
            case 1:
                ret = 11;
                break;
            case 'a':
                ret = 20;
                break;
            case '写':
                ret = 21;
                break;
            default:
                ret = 0;
        }

        return ret;
    }
}

用 javap -v 或其他工具反编译 SwitchBasicTest.class,本文使用 IDEA 插件 jclasslib Bytecode viewer

反编译得到 switchByte 方法的字节码:

 0 iload_1
 1 lookupswitch 2
        0:  28 (+27)
        1:  34 (+33)
        default:  40 (+39)
28 bipush 10
30 istore_2
31 goto 42 (+11)
34 bipush 11
36 istore_2
37 goto 42 (+5)
40 iconst_0
41 istore_2
42 iload_2
43 ireturn

switchChar 方法的字节码:

 0 iload_1
 1 lookupswitch 4
        0:  44 (+43)
        1:  50 (+49)
        97:  56 (+55)
        20889:  62 (+61)
        default:  68 (+67)
44 bipush 10
46 istore_2
47 goto 70 (+23)
50 bipush 11
52 istore_2
53 goto 70 (+17)
56 bipush 20
58 istore_2
59 goto 70 (+11)
62 bipush 21
64 istore_2
65 goto 70 (+5)
68 iconst_0
69 istore_2
70 iload_2
71 ireturn

可知对于 byte 等基础数据类型,switch 直接使用整型做比较

  • byte, short 可直接转整整值
  • char 取 unicode 整型码值
  • Integer 等包装类型,会调用 intValue 方法获得变量的整值

作用在 String 下(JDK1.7及之后版本)

public class SwitchBasicTest {
    private int switchString(String i) {
            int ret;
            switch (i) {
                case "0":
                    ret = 10;
                    break;
                case "1":
                    ret = 11;
                    break;
                case "a":
                    ret = 20;
                    break;
                default:
                    ret = 0;
            }
    
            return ret;
        }
}

使用 IDEA 编辑器打开 .class 文件看到反编译代码:

public class SwitchBasicTest {
    private int switchString(String i) {
        byte var4 = -1;
        switch(i.hashCode()) {
        case 48:
            if (i.equals("0")) {
                var4 = 0;
            }
            break;
        case 49:
            if (i.equals("1")) {
                var4 = 1;
            }
            break;
        case 97:
            if (i.equals("a")) {
                var4 = 2;
            }
        }

        byte ret;
        switch(var4) {
        case 0:
            ret = 10;
            break;
        case 1:
            ret = 11;
            break;
        case 2:
            ret = 20;
            break;
        default:
            ret = 0;
        }

        return ret;
    }
}

反编译得到 switchString 的字节码:

  0 aload_1
  1 astore_3
  2 iconst_m1
  3 istore 4
  5 aload_3
  6 invokevirtual #8 <java/lang/String.hashCode>
  9 lookupswitch 3
        48:  44 (+35)
        49:  59 (+50)
        97:  74 (+65)
        default:  86 (+77)
 44 aload_3
 45 ldc #9 <0>
 47 invokevirtual #10 <java/lang/String.equals>
 50 ifeq 86 (+36)
 53 iconst_0
 54 istore 4
 56 goto 86 (+30)
 59 aload_3
 60 ldc #11 <1>
 62 invokevirtual #10 <java/lang/String.equals>
 65 ifeq 86 (+21)
 68 iconst_1
 69 istore 4
 71 goto 86 (+15)
 74 aload_3
 75 ldc #12 <a>
 77 invokevirtual #10 <java/lang/String.equals>
 80 ifeq 86 (+6)
 83 iconst_2
 84 istore 4
 86 iload 4
 88 tableswitch 0 to 2  
        0:  116 (+28)
        1:  122 (+34)
        2:  128 (+40)
        default:  134 (+46)
116 bipush 10
118 istore_2
119 goto 136 (+17)
122 bipush 11
124 istore_2
125 goto 136 (+11)
128 bipush 20
130 istore_2
131 goto 136 (+5)
134 iconst_0
135 istore_2
136 iload_2
137 ireturn

可以看到 switch 作用在 String 上最终拆分成两个针对整型 switch 语句,具体流程为:

  • 计算并比较 hashcode
  • 如果 hashcode 相同,则进一步使用 equals 确定字符串内容相同,处理两个字符串 hashcode 相同的情况

作用在枚举下

public enum SeasonEnum {
    SPRING, SUMMER, AUTUMN, WINTER
}

public class SwitchEnumTest {
   
    private static int testSpring(SeasonEnum seasonEnum) {
        int ret;
        switch (seasonEnum) {
            case AUTUMN:
                ret = 1;
                break;
            case SPRING:
                ret = 2;
                break;
            case SUMMER:
                ret = 3;
                break;
            case WINTER:
                ret = 4;
                break;
            default:
                ret = 5;
        }
        return ret;
    }
}

反编译得到 testSpring 方法的字节码:

 0 getstatic #6 <xx/SwitchEnumTest$1.$SwitchMap$xx$enumt$SeasonEnum>
 3 aload_0
 4 invokevirtual #7 <xx/enumt/SeasonEnum.ordinal>
 7 iaload
 8 tableswitch 1 to 4   1:  40 (+32)
        2:  45 (+37)
        3:  50 (+42)
        4:  55 (+47)
        default:  60 (+52)
40 iconst_1
41 istore_1
42 goto 62 (+20)
45 iconst_2
46 istore_1
47 goto 62 (+15)
50 iconst_3
51 istore_1
52 goto 62 (+10)
55 iconst_4
56 istore_1
57 goto 62 (+5)
60 iconst_5
61 istore_1
62 iload_1
63 ireturn

可以看到:
4 invokevirtual #7 <xx/enumt/SeasonEnum.ordinal>

switch 作用在枚举上时,调用枚举的 ordinal 方法 ,即最终还是 int 值比较

lookupswitch 和 tableswitch

  • tableswitch 使用数组数据结构,通过下标可直接定位到跳转目标行
  • lookupswitch 维护了一个key-value的关系,通过逐个比较索引来查找匹配的待跳转的行数
  • tableswitchlookupswitch 查找性能更佳
  • switch语句中的 case 分支条件值比较稀疏时,tableswitch指令的空间使用率偏低,这种情况下可能使用lookupswitch 指令

结论

  • switch 语法参数实际上只对整型有效
  • break 在字节码层面上会生成一条 goto 语句,case 下不带 break 时直接进入下一个 case 逻辑
  • switch 作用在枚举上实际使用了枚举的 ordinal 方法返回值作为比较的依据
  • switch 作用在字符串上时使用字符串的 hashcode 作为比较的依据,hashcode 相同时进一步使用 equals 比较字符串内容
  • 根据java虚拟机规范,java虚拟机的 tableswitchlookupswitch 指令都只能支持int类型的条件值,所以 switch 不支持 long 长整型

资料参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容