java中的switch理解_深入理解Java的switch...case...语句 2022-11-07 14:52 471阅读 0赞 switch...case...中条件表达式的演进 最早时,只支持int、char、byte、short这样的整型的基本类型或对应的包装类型Integer、Character、Byte、Short常量 JDk1.5开始支持enum,原理是给枚举值进行了内部的编号,进行编号和枚举值的映射 1.7开始支持String,但不允许为null。(原因可以看后文) case表达式仅限字面值常量吗? case表达式既可以用字面值常量,也可以用final修饰且初始化过的变量。例如以下代码可正常编译并执行: public static int test(int i)\{ final int j = 2; int result; switch (i) \{ case 0: result = 0; break; case j: result = 1; break; case 10: result = 4; break; default: result = -1; \} return result; \} 但是没有初始化就不行,比如下面的代码就无法通过编译 public class SwitchTest\{ private final int caseJ; public int test(int i)\{ int result; switch (i) \{ case 0: result = 0; break; case caseJ: result = 1; break; case 10: result = 4; break; default: result = -1; \} return result; \} SwitchTest(int caseJ) \{ this.caseJ = caseJ; \} public static void main(String\[\] args)\{ SwitchTest testJ = new SwitchTest(1); System.out.print(testJ.test(2)); \} \} lookupswitch和tableswitch 下面两种几乎一样的代码,会编译出大相径庭的字节码。 lookupswitch public static int test(int i)\{ int result; switch (i) \{ case 0: result = 0; break; case 2: result = 1; break; case 10: result = 4; break; default: result = -1; \} return result; \} 对应字节码 public static int test(int); Code: 0: iload\_0 1: lookupswitch \{ // 3 0: 36 2: 41 10: 46 default: 51 \} 36: iconst\_0 37: istore\_1 38: goto 53 41: iconst\_1 42: istore\_1 43: goto 53 46: iconst\_4 47: istore\_1 48: goto 53 51: iconst\_m1 52: istore\_1 53: iload\_1 54: ireturn tableswitch public static int test(int i)\{ int result; switch (i) \{ case 0: result = 0; break; case 2: result = 1; break; case 4: result = 4; break; default: result = -1; \} return result; \} public static int test(int); Code: 0: iload\_0 1: tableswitch \{ // 0 to 4 0: 36 1: 51 2: 41 3: 51 4: 46 default: 51 \} 36: iconst\_0 37: istore\_1 38: goto 53 41: iconst\_1 42: istore\_1 43: goto 53 46: iconst\_4 47: istore\_1 48: goto 53 51: iconst\_m1 52: istore\_1 53: iload\_1 54: ireturn 两种字节码,最大的区别是执行了不同的指令:lookupswitch和tableswitch。 两种switch区别 tableswitch使用了一个数组,通过下标可以直接定位到要跳转的行。但是在生成字节码时,有的行可能在源码中并不存在。通过这种方式可以获得O(1)的时间复杂度。 lookupswitch维护了一个key-value的关系,通过逐个比较索引来查找匹配的待跳转的行数。而查找最好的性能是O(log n),如二分查找。 可见,通过用冗余的机器码,tableswitch换取了更好的性能。 但是,在分支比较少的情况下,O(log n)其实并不大。n=2时,log n 约为2.8;即使n=100, log n 约为 6.6,与1仍未达到1个数量级的差距。 何时生成tableswitch?何时生成lookupswitch? long table\_space\_cost = 4 + ((long) hi - lo + 1); // words long table\_time\_cost = 3; // comparisons long lookup\_space\_cost = 3 + 2 \* (long) nlabels; long lookup\_time\_cost = nlabels; int opcode = nlabels > 0 && table\_space\_cost + 3 \* table\_time\_cost <= lookup\_space\_cost + 3 \* lookup\_time\_cost ? tableswitch : lookupswitch; 这段代码的上下文: hi和lo分别代表值的上下限,是通过遍历switch...case...每个分支获取的。 nlabels表示switch...case...的分支个数 可以看出,决策的条件综合考虑了时间复杂度(table\_time\_cost/lookup\_time\_cost)和空间复杂度(table\_space\_cost/lookup\_space\_cost),并且时间复杂度的权重是空间复杂度的3倍。 存疑点: 各种幻数没有解释取值的原因,比如4、3,应该和具体细节实现有关。 lookupswitch的时间复杂度使用的是nlabels而没有取log n。此处可以看做是近似计算。 switch...case...优于if...else...吗? 一般来说,更多的限制能带来更好的性能。 从上文可以看出,无论是tableswitch还是lookupswitch,都有对随机查找的优化,而if...else...是没有的,可以看下面的源码和字节码。 public static int test2(int i)\{ int result; if(i == 0) \{ result = 0; \} else if(i == 1) \{ result = 1; \} else if(i == 4) \{ result = 4; \} else \{ result = -1; \} return result; \} public static int test2(int); Code: 0: iload\_0 1: ifne 9 4: iconst\_0 5: istore\_1 6: goto 31 9: iload\_0 10: iconst\_1 11: if\_icmpne 19 14: iconst\_1 15: istore\_1 16: goto 31 19: iload\_0 20: iconst\_4 21: if\_icmpne 29 24: iconst\_4 25: istore\_1 26: goto 31 29: iconst\_m1 30: istore\_1 31: iload\_1 32: ireturn 字符串常量的case表达式及字节码 举例如下,这段源码有两个特点: case "ghi"分支里是没有赋值代码 case "test"分支和case "test2"分支相同 public static int testString(String str)\{ int result = -4; switch (str) \{ case "abc": result = 0; break; case "def": result = 1; break; case "ghi": break; case "test": case "test2": result = 1; break; default: result = -1; \} return result; \} 对应字节码 public static int testString(java.lang.String); Code: 0: bipush -4 2: istore\_1 3: aload\_0 4: astore\_2 5: iconst\_m1 6: istore\_3 7: aload\_2 8: invokevirtual \#2 // Method java/lang/String.hashCode:()I 11: lookupswitch \{ // 5 96354: 60 99333: 74 102312: 88 3556498: 102 110251488: 116 default: 127 \} 60: aload\_2 61: ldc \#3 // String abc 63: invokevirtual \#4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 66: ifeq 127 69: iconst\_0 70: istore\_3 71: goto 127 74: aload\_2 75: ldc \#5 // String def 77: invokevirtual \#4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 80: ifeq 127 83: iconst\_1 84: istore\_3 85: goto 127 88: aload\_2 89: ldc \#6 // String ghi 91: invokevirtual \#4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 94: ifeq 127 97: iconst\_2 98: istore\_3 99: goto 127 102: aload\_2 103: ldc \#7 // String test 105: invokevirtual \#4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 108: ifeq 127 111: iconst\_3 112: istore\_3 113: goto 127 116: aload\_2 117: ldc \#8 // String test2 119: invokevirtual \#4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 122: ifeq 127 125: iconst\_4 126: istore\_3 127: iload\_3 128: tableswitch \{ // 0 to 4 0: 164 1: 169 2: 174 3: 177 4: 177 default: 182 \} 164: iconst\_0 165: istore\_1 166: goto 184 169: iconst\_1 170: istore\_1 171: goto 184 174: goto 184 177: iconst\_1 178: istore\_1 179: goto 184 182: iconst\_m1 183: istore\_1 184: iload\_1 185: ireturn 可以看到与整型常量的不同: String常量判等,先计算hashCode,在lookupswitch分支中再比较是否真正相等。这也是不支持null的原因,此时hashCode无法计算。 lookupswitch分支中,会给每个分支分配一个新下标值,作为后面的tableswitch的索引。源码中的分支语句统一在tableswitch中对应分支执行。 为什么要再生成一段tableswitch?从字节码来看,两个平行的分支("test"和"test2"),虽然没有在tableswitch中用同一个数组下标,但是使用了同一个跳转行177,在这种情况下减少了字节码冗余。 枚举的case表达式及字节码 样例代码如下 public static int testEnum(StatusEnum statusEnum)\{ int result; switch (statusEnum) \{ case INIT: result = 0; break; case FINISH: result = 1; break; default: result = -1; \} return result; \} 对应字节码 public static int testEnum(com.example.StatusEnum); Code: 0: getstatic \#9 // Field com/example/SwitchTest$1.$SwitchMap$com$example$core$service$domain$enums$StatusEnum:\[I 3: aload\_0 4: invokevirtual \#10 // Method com/example/core/service/domain/enums/StatusEnum.ordinal:()I 7: iaload 8: lookupswitch \{ // 2 1: 36 2: 41 default: 46 \} 36: iconst\_0 37: istore\_1 38: goto 48 41: iconst\_1 42: istore\_1 43: goto 48 46: iconst\_m1 47: istore\_1 48: iload\_1 49: ireturn 可以看到,使用了枚举的ordinal方法确定序号。 其他 通过查看字节码,可以发现源码的break关键字,对应的是字节码goto到具体行的语句。 如果不用break,那么对应的字节码就会“滑落”到下一行语句,继续执行。 ![4f8ab279219ace84af2c048af6abeb1a.png][] ![c136a41489130f8d2fe2426ae2cf68a8.png][] [4f8ab279219ace84af2c048af6abeb1a.png]: /images/20221023/f9842968dec546868cff9ceeb523af7e.png [c136a41489130f8d2fe2426ae2cf68a8.png]: /images/20221023/0f5ca23177414e19bda7ba4c6fb69543.png
相关 Java中的switch语句 switch多分支结构(多值情况) 语法结构: switch(表达式){ case 值1: 语句序列1; 骑猪看日落/ 2022年11月13日 13:29/ 0 赞/ 325 阅读
相关 java中的switch理解_深入理解Java的switch...case...语句 switch...case...中条件表达式的演进 最早时,只支持int、char、byte、short这样的整型的基本类型或对应的包装类型Integer、Character 旧城等待,/ 2022年11月07日 14:52/ 0 赞/ 472 阅读
相关 Java - 深入理解Java中的逃逸分析 在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两段编译,第一段是把.java文件转换成.class文件。第二段编译是把.class 叁歲伎倆/ 2022年09月08日 15:52/ 0 赞/ 185 阅读
相关 深入理解java的finalize 基本预备相关知识 1 java的GC只负责内存相关的清理,所有其它资源的清理必须由程序员手工完成。要不然会引起资源泄露,有可能导致程序崩溃。 2 调用GC并不保证G 痛定思痛。/ 2022年08月24日 11:52/ 0 赞/ 191 阅读
相关 深入理解Java中的String 一、String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码: ![复制代码][copycode.gif] public 小灰灰/ 2022年05月22日 11:51/ 0 赞/ 199 阅读
相关 【Java基础】深入理解Java中的String 一. String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码: public final class String £神魔★判官ぃ/ 2022年03月09日 06:52/ 0 赞/ 264 阅读
相关 深入理解java的finalize [2019独角兽企业重金招聘Python工程师标准>>> ][2019_Python_] ![hot3.png][] 基本预备相关知识 1 java的GC只负责内存 朱雀/ 2022年01月16日 07:15/ 0 赞/ 234 阅读
相关 深入理解Java中的AQS AQS概述 AbstractQueuedSynchronizer抽象队列同步器简称AQS,它是实现同步器的基础组件,juc下面Lock的实现以及一些并发工具类就是通过AQ 比眉伴天荒/ 2021年12月11日 09:07/ 0 赞/ 309 阅读
相关 深入理解java中的byte类型 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3 桃扇骨/ 2021年09月18日 04:44/ 0 赞/ 355 阅读
相关 Java中的Switch语句 switch 语句是一个多路分支语句。它提供了一种简单的方法,可以根据表达式的值将执行分派到代码的不同部分。基本上,表达式可以是 byte、short、char 和 in... 朱雀/ 2021年08月14日 08:51/ 0 赞/ 11593 阅读
还没有评论,来说两句吧...