字符串是关于switch的数字类型,并且总是编译为lookupswitch吗?

时间:2018-07-26 14:09:34

标签: java string jvm switch-statement

以下代码返回给定的String s是否等于任何其他硬编码的字符串。该方法使用switch语句来这样做:

public class SwitchOnString {
    public static boolean equalsAny(String s) {
        switch (s) {
        case "string 1":
            return true;
        case "string 2":
            return true;
        default:
            return false;
        }
    }
}

根据 Java虚拟机规范(JMS)3.10 Compiling Switches

  

switch语句的编译使用 tableswitch lookupswitch   说明。

而且

  

tableswitch lookupswitch 指令仅对int数据起作用。

我阅读了3.10章,但没有发现String提到的任何地方。

唯一间接接近的一句话是:

  

其他数字类型必须缩小以键入int才能在开关中使用。

问题1:
在这种情况下,String还是数字类型吗?还是我错过了什么?

javap -c上的SwitchOnString显示:

Compiled from "SwitchOnString.java"
public class playground.SwitchOnString {
  public playground.SwitchOnString();
   ...

  public static boolean equalsAny(java.lang.String);
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: invokevirtual #16                 // Method java/lang/String.hashCode:()I
       6: lookupswitch  { // 2
            1117855161: 32
            1117855162: 44
               default: 60
          }
   ...

}

很显然,hashCode值用作int的{​​{1}}键。这可能匹配:

  

lookupswitch 指令对case键(大小写的值)   标签)...

JMS说,继续进行 tableswitch lookupswitch

  

tableswitch 指令在开关的情况可以使用时使用   可以有效地表示为目标偏移量表中的索引。 (...)   如果开关的情况很少,则表的表示形式    tableswitch 指令在空间方面变得效率低下。的   可以使用 lookupswitch 指令代替。

如果我做对了,那么情况越稀疏,就越有可能使用 lookupswitch

问题2:
但是看一下字节码:
两个字符串大小写稀疏到足以将int编译为 lookupswitch 吗?还是将switch上的每个开关都编译为 lookupswitch

1 个答案:

答案 0 :(得分:1)

规范没有说明如何编译switch语句,这取决于编译器。

在这方面,JVMS语句“必须将其他数字类型的类型缩小为int以便在switch中使用”,这并不表示Java编程语言会进行这种转换,也不是说StringEnum是数字类型。即longfloatdouble是数字类型,但是不支持将它们与Java编程语言中的switch语句一起使用。

因此语言规范说,支持switch上的String,因此,编译器必须找到一种将其编译为字节码的方法。使用像哈希码这样的不变属性是一种常见的解决方案,但是原则上,也可以使用其他属性,例如长度或任意字符。

如“ Why switch on String compiles into two switches”和“ Java 7 String switch decompiled: unexpected instruction”中所述,javac当前在switch值上编译String时在字节码级别上生成两个切换指令(ECJ还会生成两条指令,但细节可能有所不同。

然后,编译器必须选择一条lookupswitchtableswitch指令。当数字不稀疏时,javac确实会使用tableswitch,但前提是该语句具有两个以上的大小写标签。

因此,当我编译以下方法时:

public static char two(String s) {
    switch(s) {
        case "a": return 'a';
        case "b": return 'b';
    }
    return 0;
}

我明白了

public static char two(java.lang.String);
Code:
   0: aload_0
   1: astore_1
   2: iconst_m1
   3: istore_2
   4: aload_1
   5: invokevirtual #9                  // Method java/lang/String.hashCode:()I
   8: lookupswitch  { // 2
                97: 36
                98: 50
           default: 61
      }
  36: aload_1
  37: ldc           #10                 // String a
  39: invokevirtual #11                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  42: ifeq          61
  45: iconst_0
  46: istore_2
  47: goto          61
  50: aload_1
  51: ldc           #12                 // String b
  53: invokevirtual #11                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  56: ifeq          61
  59: iconst_1
  60: istore_2
  61: iload_2
  62: lookupswitch  { // 2
                 0: 88
                 1: 91
           default: 94
      }
  88: bipush        97
  90: ireturn
  91: bipush        98
  93: ireturn
  94: iconst_0
  95: ireturn

但是当我编译时,

public static char three(String s) {
    switch(s) {
        case "a": return 'a';
        case "b": return 'b';
        case "c": return 'c';
    }
    return 0;
}

我明白了

public static char three(java.lang.String);
Code:
   0: aload_0
   1: astore_1
   2: iconst_m1
   3: istore_2
   4: aload_1
   5: invokevirtual #9                  // Method java/lang/String.hashCode:()I
   8: tableswitch   { // 97 to 99
                97: 36
                98: 50
                99: 64
           default: 75
      }
  36: aload_1
  37: ldc           #10                 // String a
  39: invokevirtual #11                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  42: ifeq          75
  45: iconst_0
  46: istore_2
  47: goto          75
  50: aload_1
  51: ldc           #12                 // String b
  53: invokevirtual #11                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  56: ifeq          75
  59: iconst_1
  60: istore_2
  61: goto          75
  64: aload_1
  65: ldc           #13                 // String c
  67: invokevirtual #11                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  70: ifeq          75
  73: iconst_2
  74: istore_2
  75: iload_2
  76: tableswitch   { // 0 to 2
                 0: 104
                 1: 107
                 2: 110
           default: 113
      }
 104: bipush        97
 106: ireturn
 107: bipush        98
 109: ireturn
 110: bipush        99
 112: ireturn
 113: iconst_0
 114: ireturn

尚不清楚为什么javac做出此选择。与tableswitch相比,lookupswitch的基本占用空间(一个额外的32位字)更高,即使对于两个case标签的情况,它的字节码仍会更短。

但是可以通过第二条语句显示决策的一致性,第二条语句将始终具有相同的值范围,但是仅取决于标签的数量才能编译为lookupswitchtableswitch。因此,当使用真正的稀疏值时:

public static char three(String s) {
    switch(s) {
        case "a": return 'a';
        case "b": return 'b';
        case "": return 0;
    }
    return 0;
}

它编译为

public static char three(java.lang.String);
Code:
   0: aload_0
   1: astore_1
   2: iconst_m1
   3: istore_2
   4: aload_1
   5: invokevirtual #9                  // Method java/lang/String.hashCode:()I
   8: lookupswitch  { // 3
                 0: 72
                97: 44
                98: 58
           default: 83
      }
  44: aload_1
  45: ldc           #10                 // String a
  47: invokevirtual #11                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  50: ifeq          83
  53: iconst_0
  54: istore_2
  55: goto          83
  58: aload_1
  59: ldc           #12                 // String b
  61: invokevirtual #11                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  64: ifeq          83
  67: iconst_1
  68: istore_2
  69: goto          83
  72: aload_1
  73: ldc           #13                 // String
  75: invokevirtual #11                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
  78: ifeq          83
  81: iconst_2
  82: istore_2
  83: iload_2
  84: tableswitch   { // 0 to 2
                 0: 112
                 1: 115
                 2: 118
           default: 120
      }
 112: bipush        97
 114: ireturn
 115: bipush        98
 117: ireturn
 118: iconst_0
 119: ireturn
 120: iconst_0
 121: ireturn

对于稀疏哈希码使用lookupswitch,对于第二个开关使用tableswitch