【问题标题】:Why can't I have a duplicate case in my switch statement?为什么我的 switch 语句中不能有重复的大小写?
【发布时间】:2014-06-04 20:18:03
【问题描述】:

我知道这将无法编译:

int caseNum = 2;

switch(caseNum)
{
    case 2:
        System.out.println("Happy");
        break;
    case 2:
        System.out.println("Birthday");
        break;
    case 2:
        System.out.println("To the ground!");
        break;
    default:
        System.out.println("<3");
        break;
}

我知道 case 语句相互冲突,并且编译器“不知道我在说哪个 'case 2'”。我和我的一些同行在幕后想知道冲突是什么,并且听说 switch 语句被转换为哈希映射。是这样吗,switch语句在编译期间是否会变成哈希映射,并且映射中的冲突会产生错误?

到目前为止,我已经在 Stack Overflow 和 Google 上寻找答案,并且信息必须已经存在,但我不确定如何正确表达它会出现的问题。提前致谢!

【问题讨论】:

  • 两个 case 的值相同是没有意义的,因为你可以将两个 case 的代码放在一个 case 中。实现细节不是原因,这只是这种构造语言的制作方式。
  • 如前所述,我知道这一点,我想知道 switch 语句的实现在幕后是什么。

标签: java compiler-construction compiler-errors


【解决方案1】:

哈希映射只是编译 switch 语句的一种方式,但在任何情况下,您都可以将重复的 cases 想象为试图在常规 HashMap 中为同一个键具有多个值。编译器不知道哪个值对应于键,因此会发出错误。

switch 语句也可以编译成一个跳转表,在这种情况下,由于非常相似的原因,这仍然是模棱两可的——同一跳转位置有多种不同的可能性。

switch 语句也可以编译成二进制搜索,在这种情况下,您仍然会遇到同样的问题——搜索相同键的多个不同结果。


以防你好奇,我做了一个小测试用例来看看javac 会将switch 编译成什么。从这个(稍作修改)来源:

public static void main(final String[] args) {
    final int caseNum = 2;

    switch (caseNum) {
        case 1:
            System.out.println("Happy");
            break;
        case 2:
            System.out.println("Birthday");
            break;
        case 3:
            System.out.println("To the ground!");
            break;
        default:
            System.out.println("<3");
            break;
    }
}

你得到这个字节码:

public static void main(java.lang.String[]);
    Code:
       0: iconst_2      
       1: tableswitch   { // 1 to 3
                     1: 28
                     2: 39
                     3: 50
               default: 61
          }
      28: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: ldc           #3                  // String Happy
      33: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      36: goto          69
      39: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      42: ldc           #5                  // String Birthday
      44: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      47: goto          69
      50: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      53: ldc           #6                  // String To the ground!
      55: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      58: goto          69
      61: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      64: ldc           #7                  // String <3
      66: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      69: return        

所以至少对于这个小的switch,似乎使用了跳转表。我听说开关的编译方式部分取决于它们的大小,但我不知道实现更改的确切点。 20个案例好像还是用跳表实现的……


原来String switch 语句的实现有点不同。从这个来源:

public static void main(final String[] args) {
    final String caseNum = "2";

    switch (caseNum) {
        case "1":
            System.out.println("Happy");
            break;
        case "2":
            System.out.println("Birthday");
            break;
        case "3":
            System.out.println("To the ground!");
            break;
        default:
            System.out.println("<3");
            break;
    }
}

你得到这个字节码:

public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String 2
       2: astore_2      
       3: iconst_m1     
       4: istore_3      
       5: aload_2       
       6: invokevirtual #3                  // Method java/lang/String.hashCode:()I
       9: tableswitch   { // 49 to 51
                    49: 36
                    50: 50
                    51: 64
               default: 75
          }
      36: aload_2       
      37: ldc           #4                  // String 1
      39: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      42: ifeq          75
      45: iconst_0      
      46: istore_3      
      47: goto          75
      50: aload_2       
      51: ldc           #2                  // String 2
      53: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      56: ifeq          75
      59: iconst_1      
      60: istore_3      
      61: goto          75
      64: aload_2       
      65: ldc           #6                  // String 3
      67: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      70: ifeq          75
      73: iconst_2      
      74: istore_3      
      75: iload_3       
      76: tableswitch   { // 0 to 2
                     0: 104
                     1: 115
                     2: 126
               default: 137
          }
     104: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
     107: ldc           #8                  // String Happy
     109: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     112: goto          145
     115: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
     118: ldc           #10                 // String Birthday
     120: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     123: goto          145
     126: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
     129: ldc           #11                 // String To the ground!
     131: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     134: goto          145
     137: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
     140: ldc           #12                 // String <3
     142: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     145: return

所以你仍然有跳跃表,但你有两个。有点迂回的过程——取散列码,打开它,根据情况加载另一个常数,然后做一个“正常”的开关​​(我认为)。但我想是相同的基本过程?

【讨论】:

    【解决方案2】:

    从历史上看,switch 语句曾经/可以实现为跳转表(即值到目标地址的映射)。根据表的实现,可能不可能有两个相同值的条目指向不同的地址。即使有可能,您将如何处理该重复值?从第一个处理程序返回,然后转到第二个处理程序?从不执行第二个处理程序?

    允许这样做是没有意义的。

    【讨论】:

    • 你说得对,我可以将我的问题改写为“为什么编程中的歧义不好?”我只是想知道 switch 语句是否在编译时被转换为其他东西之前和除了计算机读取的字节码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-27
    • 1970-01-01
    • 1970-01-01
    • 2012-10-04
    相关资源
    最近更新 更多