为什么我的 switch 语句中不能有重复的 case?

问题描述 投票:0回答:2

我知道这将无法编译:

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 上寻找答案,并且信息肯定已经存在,但我不确定如何正确表达它会出现的问题。 预先感谢!

java compiler-errors switch-statement
2个回答
7
投票

哈希映射只是编译 switch 语句的一种方式,但在任何情况下,您都可以将重复的

case
想象为尝试在常规
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

所以你仍然有跳转表,但你有两个。有点迂回的过程——获取哈希码,根据情况加载另一个常量,然后进行“正常”切换(我认为)。但我想基本过程是一样的?


0
投票

从历史上看,

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

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

© www.soinside.com 2019 - 2024. All rights reserved.