【问题标题】:Why switch on String compiles into two switches为什么switch on String会编译成两个switch
【发布时间】:2014-10-23 11:41:07
【问题描述】:

我阅读了关于编译开关的JVM specification,并对如何编译 String 上的 switch 语句产生了兴趣。这是我检查的测试方法(JDK1.7.0_40):

static int test(String i) {
    switch (i) {
        case "a":  return  -100;
        case "45b":  return  1;
        case "c":  return  2;
        default: return -1;
    }
}

我希望这个方法可以编译成简单的lookupswitch on hashCode的字符串,但是突然

static int test(java.lang.String);
Code:
   0: aload_0
   1: astore_1
   2: iconst_m1
   3: istore_2
   4: aload_1
   5: invokevirtual #6         // Method java/lang/String.hashCode:()I
   8: lookupswitch  { // 3
                97: 44
                99: 72
             51713: 58
           default: 83
      }
  44: aload_1
  45: ldc           #7 // String a
  47: invokevirtual #8 // 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           #9  // String 45b
  61: invokevirtual #8  // 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           #10 // String c
  75: invokevirtual #8  // 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: 117
           default: 119
      }
 112: bipush        -100
 114: ireturn
 115: iconst_1
 116: ireturn
 117: iconst_2
 118: ireturn
 119: iconst_m1
 120: ireturn

如您所见,在第一个 lookupswitch 的分支中,JVM 并没有做真正的工作,而是为后续的 tableswitch(第 84 行)生成索引。
Tableswitch 应该工作得很快,所以不会带来很多额外的工作。但是无论如何,生成额外开关的目的是什么?
更新
我了解 hashCode 冲突的可能性。我想说的是,编译器可以将所有实际工作从后续 tableswitch 移到 first,然后使用 ifeq 跳转到所有 switch 分支的末尾,而不是后续的 tableswitch。 所以我在这里看到的一个可能的答案是:在第一个 switch 编译器尝试根据已知数量的情况为 ifeq 跳转预先计算标签,但我不确定这是唯一的原因。

更新2
正如 @ericbn 所建议的,我尝试编译

switch (i) { 
   case 97: return -100; 
   case 51713: return 1; 
   case 99: return 2; 
   default: return -1;
}

将 i 作为 int,编译器给了我简单的查找开关。

【问题讨论】:

  • 由于鸽巢原理,不同的字符串可以具有相同的 hashCode。我认为这就是它进行两次切换的原因。
  • 你不认为只有一个开关吗?第二个开关用于条件返回。
  • @HuStmpHrrr 第二个开关引入了一个明显没有动机的间接层,从表中查找的跳转可以直接插入到第一个开关块中作为goto 目标。
  • @HuStmpHrrr 我试图修改代码并且不要在 switch 中使用 return 语句。无论如何,编译成两个开关。此外,如果我在整数上使用开关,则编译为一个。
  • 试着用 int:switch (i) { case 97: return -100; case 51713: return 1; case 99: return 2; default: return -1; } 查看 switch 的编译代码,并检查生成的“switches”...

标签: java jvm switch-statement java-7 bytecode


【解决方案1】:

来自javac source code的引用:

         * The general approach used is to translate a single
         * string switch statement into a series of two chained
         * switch statements: the first a synthesized statement
         * switching on the argument string's hash value and
         * computing a string's position in the list of original
         * case labels, if any, followed by a second switch on the
         * computed integer value.  The second switch has the same
         * code structure as the original string switch statement
         * except that the string case labels are replaced with
         * positional integer constants starting at 0.
         *
         * The first switch statement can be thought of as an
         * inlined map from strings to their position in the case
         * label list.  An alternate implementation would use an
         * actual Map for this purpose, as done for enum switches.
         *
         * With some additional effort, it would be possible to
         * use a single switch statement on the hash code of the
         * argument, but care would need to be taken to preserve
         * the proper control flow in the presence of hash
         * collisions and other complications, such as
         * fallthroughs.  Switch statements with one or two
         * alternatives could also be specially translated into
         * if-then statements to omit the computation of the hash
         * code.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-04-22
    • 1970-01-01
    • 2020-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多