benf.org :  other :  cfr :  Java 7 Switch On String

Aside - Java 7 switch on string

Since this is just about usable now, I thought I'd try it with some of the java 7 features. I had wondered how switch on string worked. I was just about right, (like so much in java-land, it's all syntactic sugar) but it still leaves me feeling rather sad.

(This isn't a secret - the original proposal is here, but, like I mention elsewhere, reverse engineering this stuff is fun!)

Original code

    int test0(String s) {
        switch (s) {
            default:
                System.out.println("Test");
                break;
            case "BB":  // BB and Aa have the same hashcode.
                return 12;
            case "Aa":
            case "FRED":
                return 13;
        }
        System.out.println("Here");
        return 0;
    }

CFR 0_6 decompilation

With the nostringswitch parameter, we can tell CFR not to re-sugar string switches. This lets us see the code that javac has created - effectively it's creating a hardcoded hashtable with chaining for the first switch...

public int test0(String s)
{
    unnamed_local_s_2 = s;
    unnamed_local_s_3 = -1;
    switch (unnamed_local_s_2.hashCode()) {
        case 2112: {
            if (unnamed_local_s_2.equals("Aa")) {
                unnamed_local_s_3 = 2;
                break;
            }
            if (!(unnamed_local_s_2.equals("BB"))) break;
            unnamed_local_s_3 = 1;
            break;
        }
        case 2166379: {
            if (!(unnamed_local_s_2.equals("FRED"))) break;
            unnamed_local_s_3 = 3;
        }
    }
    switch (unnamed_local_s_3) {
        default: {
            System.out.println("Test");
            break;
        }
        case 1: {
            return 12;
        }
        case 2: case 3: {
            return 13;
        }
    }
    System.out.println("Here");
    return 0;
}

It relies on (quite reasonable) the fact that since the buggy early version of hashcode was fixed, java's String hashcode has an explicit algorithm, which is fair enough. But it needs the two pass switch to handle fallthrough and collisions... :P

Re-sugaring

If you don't specify --nostringswitch, as of 0_6, cfr will resugar the switch, and output the following

public int test0(String s)
{
    switch (s) {
        default: {
            System.out.println("Test");
            break;
        }
        case "BB": {
            return 12;
        }
        case "Aa": case "FRED": {
            return 13;
        }
    }
    System.out.println("Here");
    return 0;
}

Opcodes

For interests sake, here is javap's output

  public int test0(java.lang.String);
    Code:
       0: aload_1       
       1: astore_2      
       2: iconst_m1     
       3: istore_3      
       4: aload_2       
       5: invokevirtual #2                  // Method java/lang/String.hashCode:()I
       8: lookupswitch  { // 2
                  2112: 36
               2166379: 64
               default: 75
          }
      36: aload_2       
      37: ldc           #3                  // String Aa
      39: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      42: ifeq          50
      45: iconst_2      
      46: istore_3      
      47: goto          75
      50: aload_2       
      51: ldc           #5                  // String BB
      53: invokevirtual #4                  // 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 FRED
      67: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      70: ifeq          75
      73: iconst_3      
      74: istore_3      
      75: iload_3       
      76: tableswitch   { // 1 to 3
                     1: 115
                     2: 118
                     3: 118
               default: 104
          }
     104: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
     107: ldc           #8                  // String Test
     109: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     112: goto          121
     115: bipush        12
     117: ireturn       
     118: bipush        13
     120: ireturn       
     121: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
     124: ldc           #10                 // String Here
     126: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     129: iconst_0      
     130: ireturn       

Last updated 02/2013