欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java关于switch的基础使用详解

程序员文章站 2022-05-03 23:48:33
前段时间看见一道java的基础题,到现在时间过去的有点久,一模一样的原题是记不清了,还有个模糊印象,大概是这样: public static void main(String[] args) { int i = 4; switch (i) { default: System.out.println("default"); case 1: System.o...

前段时间看见一道java的基础题,到现在时间过去的有点久,一模一样的原题是记不清了,还有个模糊印象,大概是这样:

 public static void main(String[] args) {
        int i = 4;
        switch (i) {
            default:
                System.out.println("default");
            case 1:
                System.out.println("i = 1");
            case 2:
                System.out.println("i = 2");
                break;
            case 31:
                System.out.println("i = 31");
                break;
        }
    }

输出是多少?

emmmm~~~~~~,我承认,这题是很简单很基础,虽然我很不好意思,但是我看见这道题是那一秒,有点懵。这么多年写代码,default一般都是放在最后,要么就可能不写,这是第一次看见,把default放最前面。

当时思索了下,答案应该是输出:

default
i = 1
i = 2

到i=2这里时,遇到break就跳出去了。

后来验证了下,确实是这个答案。

后来反省了下,为什么会出现类似我这种状态。在我看来,有的人遇到这道题马上能看出正确答案,有的可能跟我差不多,先懵一下。我想自己虽然已经写了几年java,会出现这种情况,应该是我最初的认知加固有的思维导致的。

我平常一直认为,case就是从上往下比对,直到遇到匹配的,处理,然后有break就break,没有继续往下走直到遇到break;或者从上往下都没匹配到,就到default了。一直都是这样理解的,所以当时第一眼看到default在最上面然后懵的时候,心理想的是:我去,它怎么一开始就知道后面匹配不到,要走到default,再加上i=1后面没有break的影响,然后想的是,莫非最后要走完再走回去到default那里?

随后看了下字节码,明白原因了。

先看下我们常规写法:

 public static void main(String[] args) {
        int i = 4;
        switch (i) {
            case 1:
                System.out.println("i = 1");
            case 2:
                System.out.println("i = 2");
                break;
            case 31:
                System.out.println("i = 31");
                break;
            default:
                System.out.println("default");
        }
    }

这样写,我觉得搁谁,不用想都知道输出:default。

看下它的字节码(这里不讨论关于switch的tableswitch和lookupswitch的问题,本来最后一个是case 3,为了避免常规写法编译后为tableswitch,我特意改为31,让数值大点,都是lookupswitch,减少要讨论的点。如果是tableswitch的话,确实是不用顺序比较,直接就跳到default对应的位置了):

 public static void main(java.lang.String[]);
    Code:
       0: iconst_4
       1: istore_1
       2: iload_1
       3: lookupswitch  { // 3
                     1: 36
                     2: 44
                    31: 55
               default: 66
          }
      36: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      39: ldc           #3                  // String i = 1
      41: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      44: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      47: ldc           #5                  // String i = 2
      49: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      52: goto          74
      55: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      58: ldc           #6                  // String i = 3
      60: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      63: goto          74
      66: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      69: ldc           #7                  // String default
      71: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      74: return
}

从上面偏移量为3的指令:lookupswitch那里,case 1,2,31和default对应到的指令偏移量是递增的,所以最后到defautl跳转到偏移量66处。最后执行完就return了。

看下default在最上面的字节码:

 public static void main(java.lang.String[]);
    Code:
       0: iconst_4
       1: istore_1
       2: iload_1
       3: lookupswitch  { // 3
                     1: 44
                     2: 52
                    31: 63
               default: 36
          }
      36: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      39: ldc           #3                  // String default
      41: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      44: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      47: ldc           #5                  // String i = 1
      49: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      52: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      55: ldc           #6                  // String i = 2
      57: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: goto          71
      63: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      66: ldc           #7                  // String i = 31
      68: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      71: return
}

可以看到它的lookupswitch那里,default还是最后的,会顺序比较完,但是default后面指向的指令偏移量在其它几种情况前面,和源码位置对应,所以会从偏移量36处理开始执行,因为case 1没有break,向下滑落,一直到打印完case 2才goto到return。

所以,,,就是这么简单的道理~~~,尽管把default放最前面,编译后lookupswitch这种情况,default还是到最后,其它比较完了都不匹配的处理情况。

另外,在看字节码测试的过程中,发现了一个很有意思的现象,如果只有case 1,2(2种情况)和default这个场景,编译后都是lookupswitch;如果有case1,2,3(3种情况)和default,编译后为tableswitch;所以我才把case 设置1,2,31的时候,编译后还是lookupswitch。

关于选择tableswitch和lookupswitch的实现部分,这是jdk8的源码:http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/30db5e0aaf83/src/share/classes/com/sun/tools/javac/jvm/Gen.java#l1129

下面,摘了关键的一部分:

 // Compute number of labels and minimum and maximum label values.
            // For each case, store its label in an array.
            int lo = Integer.MAX_VALUE;  // minimum label.
            int hi = Integer.MIN_VALUE;  // maximum label.
            int nlabels = 0;               // number of labels.

            int[] labels = new int[cases.length()];  // the label array.
            int defaultIndex = -1;     // the index of the default clause.

            List<JCCase> l = cases;
            for (int i = 0; i < labels.length; i++) {
                if (l.head.pat != null) {
                    int val = ((Number)l.head.pat.type.constValue()).intValue();
                    labels[i] = val;
                    if (val < lo) lo = val;
                    if (hi < val) hi = val;
                    nlabels++;
                } else {
                    assert defaultIndex == -1;
                    defaultIndex = i;
                }
                l = l.tail;
            }

            // Determine whether to issue a tableswitch or a lookupswitch
            // instruction.
            // 关键是这里选择是table还是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; 

看我中文注释的地方:

这里计算一下,如果 是case 1,2 的时候,这里case label没有default。如果是1,2的从上面往下算是,hi是2,lo是1,nlabels是2,所以:

int opcode =
                nlabels > 0 &&
                table_space_cost + 3 * table_time_cost <=
                lookup_space_cost + 3 * lookup_time_cost
                ?
                tableswitch : lookupswitch;

这里判断就是:opcode = 2 > 0 && 6 + 3 * 3 <= 7 + 3 * 2 ? tableswitch: lookupswitch。返回的是lookupswitch。

如果是case 1,2,3就是:opcode = 3 > 0 && 7 + 3 * 3 <= 9 + 3 * 3 ? tableswitch: lookupswitch。返回的是tableswitch。

如果是case 1,2,31就是:opcode = 3 > 0 && 35 + 3 * 3 <= 9 + 3 * 3 ? tableswitch: lookupswitch。返回的是lookupswitch。

这个计算不复杂,其它情况自己比对。

关于tableswitch和lookupswitch不是关注点就不再多说。

本文地址:https://blog.csdn.net/x763795151/article/details/108030273

相关标签: switch Java