转载

说说Java中你不知道switch关键字的奥秘

switch 作为Java内置关键字,却在项目中真正使用的比较少。关于 switch ,还是有那么一些 奥秘 的。

说说Java中你不知道switch关键字的奥秘

要什么switch,我有if-else

确实,项目中使用 switch 比较少的一个主要原因就在于它的作用能被 if-else 代替,况且 switch 对类型的限制,也阻碍了 switch 的进一步使用。

先看看 switch 的语法:

switch(exp){
    case exp1:
        break;
    case exp2:
        break;
    default:
        break;
}
复制代码

其中 exp 的类型限制为: byte ,short , int , char, 及其包装类,以及枚举和 String (JDK1.7)

为什么要有这些限制?

如果说, switch 的功能和 if-else 的一模一样,那么它存在的意义在哪里?

答案是: switchif-else 在设计的时候,是有一定的性能差别的。

看代码:

public class Test {

    public static void switchTest(int a) {

        switch (a) {
            case 1:
                System.out.println("1");
                break;
            case 2:
                System.out.println("2");
                break;
            default:
                System.out.println("3");
                break;
        }
    }
}

复制代码
javap  -c Test.class
复制代码

结果如下:

public static void switchTest(int);
    Code:
       0: iload_0
       1: lookupswitch  { // 2
                     1: 28
                     2: 39
               default: 50
          }
          
	...

复制代码

这里面省略一些代码。

可以发现, switch 是通过 lookupswitch 指令实现。那么 lookupswitch 指令是干嘛的呢?

在 Java se8 文档中的描述可以大概知道:

switch 可以被编译为两种指令

  • lookupswitch :当 switchcase 比较稀疏的时候,使用该指令对 int 值的 case 进行一一比较,直至找到对应的 case (这里的查找,可以优化为二分查找)
  • tableswitch :当 switchcase 比较密集的时候,使用 case 的值作为 switch 的下标,可以在时间复杂度为O(1)的情况下找到对应的 case (可以类比HashMap)

并且文档中还有一段描述:

Java虚拟机的 tableswitchlookupswitch 指令仅对 int 数据有效。因为对 bytechar 或或 short 值的操作在内部被提升为 int ,所以对其 switch 表达式求值为其中一个类型进行编译,就好像它被计算为要键入一样 int 。如果 chooseNear 方法是使用type编写的,则使用类型时 short 将生成相同的Java虚拟机指令 int 。其他数字类型必须缩小到类型 int 以便在a中使用 switch

现在,我们应该能够明白,为什么 switch 关键字会有类型限制了,因为 switch 所被翻译的关键字是被限制为int类型的 ,至于为什么是int,我猜应该是基于性能和实现的复杂度的考量吧。

int之外的类型

我们明白了 byte,shor,char,int 能被作为 switch 类型后,再看看枚举和 String

public static void switchTest(String a) {

        switch (a) {
            case "1":
                System.out.println("1");
                break;
            case "2":
                System.out.println("2");
                break;
            default:
                System.out.println("3");
                break;
        }
    }
复制代码

编译生成Test.class。拖入IDEA进行反编译得到如下代码:

public static void switchTest(String a) {
        byte var2 = -1;
        switch(a.hashCode()) {
        case 49:
            if (a.equals("1")) {
                var2 = 0;
            }
            break;
        case 50:
            if (a.equals("2")) {
                var2 = 1;
            }
        }

        switch(var2) {
        case 0:
            System.out.println("1");
            break;
        case 1:
            System.out.println("2");
            break;
        default:
            System.out.println("3");
        }

    }
复制代码

可以看见,JDK7 所支持的 String 类型是通过获取 String 的hashCode来进行选择的,也就是本质上还是int.为什么 String 可以这样干?这取决于 String 是一个不变类。

为了防止hash碰撞,代码更加保险的进行了 equals 判断。

再来看看 Enum

public static void switchTest(Fruit a) {
    switch (a) {
        case Orange:
            System.out.println("Orange");
            break;
        case Apple:
            System.out.println("Apple");
            break;
        default:
            System.out.println("Banana");
            break;
    }

}
复制代码

编译生成Test.class。拖入IDEA进行反编译得到如下代码:

public static void switchTest(Fruit a) {
        switch(1.$SwitchMap$com$dengchengchao$Fruit[a.ordinal()]) {
        case 1:
            System.out.println("Orange");
            break;
        case 2:
            System.out.println("Apple");
            break;
        default:
            System.out.println("Banana");
        }

    }
复制代码

可以看到,枚举支持 switch 更加简单,直接通过枚举的顺序即可作为相关 case

总之:

  • switch 的设计按道理来说,是比 if-else 要快的,但是在99.99%的情况下,他们性能差不多,除非 case 分支量巨大,但是在 case 分支过多的情况下,一般应该考虑使用多态重构了。
  • switch 虽然支持 byte,int,short,char,enum,String 但是本质上都是 int ,其他的只是编译器帮你进行了语法糖优化而已。

尊重劳动成果,转载注明出处

如果觉得写得不错,欢迎关注微信公众号:逸游Java ,每天不定时发布一些有关Java进阶的文章,感谢关注

说说Java中你不知道switch关键字的奥秘
原文  https://juejin.im/post/5dc7ea8af265da4d082b93fb
正文到此结束
Loading...