深入理解Java的switch...case...语句 您所在的位置:网站首页 switchcase语句的用法java 深入理解Java的switch...case...语句

深入理解Java的switch...case...语句

2024-04-30 01:56| 来源: 网络整理| 查看: 265

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?

在JDK1.8环境下,通过检索langtools这个包,可以在langtools/src/share/classes/com/sun/tools/javac/jvm/Gen.java看到以下代码:

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 Tools->External Tools,点击+,按如下页面配置即可。

Windows下需要将上图填入的javap改为javap.exe。

注意:每次查看字节码前,要确保对应类被重新编译,才能看到最新版。

附2——JDK7或8下,switch...case...使用字符串常量编译报错解决方式

这种情况的真实原因是,JDK设置不一致,IDE没有完全使用预期的编译器版本。 在IDEA里可以这样解决: Project Settings -> Project 设置项目语言 如果仍未解决,检查 File -> Project Structure -> Modules, 查看所有模块是否都是预期的等级。 还有一处也可以看下File -> Settings -> Compiler -> Java Compiler. 这里可以设置项目及模块的编译器版本。

备注

文中所有log n均为以2为底n的对数。 本文的写作契机是参加公司的XX安全学习,提到了switch...case...和if...else...的性能有差异,因此花了一天研究了一番。

参考文档

通过字节码分析java中的switch语句 Difference between JVM's LookupSwitch and TableSwitch? IntelliJ switch statement using Strings error: use -source 7 Intellij idea快速查看Java类字节码



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有