javacompiler-errorsswitch-statement

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


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!


Solution

  • A hash map is just one way that a switch statement could be compiled, but in any case, you can imagine having duplicate cases 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?