Why can't I have a duplicate case in my switch statement?

compiler-construction, compiler-errors, java

Solution

A hash map is just one way that a switch statement could be compiled, but in any case, you can imagine having duplicate `case`s as trying to have multiple values for the same key in a regular `HashMap`. The compiler doesn't know which one of the values corresponds to the key, and so emits an error.

`switch` statements could also be compiled into a jump table, in which case this is still ambiguous for a very similar reason -- you have multiple different possibilities for the same jump location.

`switch` statements could also be compiled into a binary search, in which case you still have the same problem -- multiple different results for the same key being searched for.

Just in case you were curious, I did a small test case to see what `javac` would compile the `switch` to. From this (slightly modified) source:

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;
    }
}

You get this bytecode:

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        

So at least for this small `switch`, a jump table seems to be used. I have heard that the way switches are compiled depends partly on their size, but I don't know the exact point at which the implementation changes. 20 cases still seem to be implemented as a jump table...

Turns out `String` switch statements are implemented a bit differently. From this source:

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;
    }
}

You get this bytecode:

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

So you still have jump tables, but you have two. Bit of a roundabout process -- take the hash code, switch on that, based on the case load another constant, and from that do a "normal" switch (I think). But same basic process, I suppose?

Problem

I understand that this will fail to compile: ``` 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; } ``` I know that the case statements are conflicting and that the compiler "doesn't know which 'case 2' I am talking about". A few peers of mine and myself were wondering behind the scenes what the conflict is and had heard that switch statements are converted into hash-maps. Is that the case, does a switch statement become a hash-map during compile time and the conflict in the mapping create the error? So far I have looked around Stack Overflow and Google for an answer, and the information must be out there already, but I am unsure how to express the question correctly it would appear. Thanks in advance!

Original source