Stephen B.
Stephen B.

Reputation: 204

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!

Upvotes: 3

Views: 5319

Answers (2)

awksp
awksp

Reputation: 11877

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?

Upvotes: 7

nobody
nobody

Reputation: 20174

Historically, switch statements were/could be implemented as jump tables (i.e. a mapping of value to destination address). Depending on the implementation of the table, it may be impossible to have two entries for the same value pointing to different addresses. Even if it were possible, how would you handle that duplicate value? Return from the first handler and then go to second handler? Never execute the second handler?

It makes no sense to allow this.

Upvotes: 0

Related Questions