2014-08-29 7 views
17

I JVM specification zum Kompilieren Schalter lesen und wurde interessiert, wie switch-Anweisung auf String zusammengestellt. Hier ist die Testmethode I geprüft (JDK1.7.0_40):Warum Schalter auf String kompiliert in zwei Schalter

static int test(String i) { 
    switch (i) { 
     case "a": return -100; 
     case "45b": return 1; 
     case "c": return 2; 
     default: return -1; 
    } 
} 

ich diese Methode erwarten zusammengestellt in einfache lookupswitch auf hashCode Schnur an, aber plötzlich

static int test(java.lang.String); 
Code: 
    0: aload_0 
    1: astore_1 
    2: iconst_m1 
    3: istore_2 
    4: aload_1 
    5: invokevirtual #6   // Method java/lang/String.hashCode:()I 
    8: lookupswitch { // 3 
       97: 44 
       99: 72 
      51713: 58 
      default: 83 
     } 
    44: aload_1 
    45: ldc   #7 // String a 
    47: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 
    50: ifeq   83 
    53: iconst_0 
    54: istore_2 
    55: goto   83 
    58: aload_1 
    59: ldc   #9 // String 45b 
    61: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 
    64: ifeq   83 
    67: iconst_1 
    68: istore_2 
    69: goto   83 
    72: aload_1 
    73: ldc   #10 // String c 
    75: invokevirtual #8 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 
    78: ifeq   83 
    81: iconst_2 
    82: istore_2 
    83: iload_2 
    84: tableswitch { // 0 to 2 
       0: 112 
       1: 115 
       2: 117 
      default: 119 
     } 
112: bipush  -100 
114: ireturn 
115: iconst_1 
116: ireturn 
117: iconst_2 
118: ireturn 
119: iconst_m1 
120: ireturn 

Wie man sehen konnte, in Zweigen des ersten Nachschlagewerkes macht JVM nicht die wirkliche Arbeit, stattdessen Indizes für den nachfolgenden Tabellenschalter (Zeile 84) zu erzeugen.
TableSwitch sollte schnell arbeiten, bringt also nicht viel zusätzliche Arbeit. Aber was ist der Zweck, zusätzliche Schalter zu generieren?
Update
Ich verstehe die Möglichkeit von HashCode Kollisionen. Was ich zu sagen versuche, ist, dass der Compiler anstelle des nachfolgenden Tablewitch die gesamte echte Arbeit aus dem nachfolgenden tableswitch in den ersten verschieben und ifeq dann verwenden kann, um zum Ende aller Switch-Zweige zu springen. So die eine mögliche Antwort sehe ich hier: dass in ersten Switch Compiler versucht, Etikett für ifeq Sprung basierend auf bekannten Anzahl von Fällen vorberechnen, aber ich bin mir nicht sicher, dass es der einzige Grund ist.

Update2
Als @ericbn vorgeschlagen, versuchte ich

switch (i) { 
    case 97: return -100; 
    case 51713: return 1; 
    case 99: return 2; 
    default: return -1; 
} 

mit i zu kompilieren als int und Compiler mir schlicht lookupswitch gab.

+0

Distinct Saite (n) die gleiche hashCode aufgrund der Schubfachprinzip haben. Ich denke, deshalb gibt es zwei Schalter. –

+2

Glaubst du nicht, dass es nur einen Schalter gibt? der zweite Schalter ist für die bedingte Rückkehr. – HuStmpHrrr

+1

@HuStmpHrrr Der zweite Schalter führt eine scheinbar unmotivierte Dereferenzierungsschicht ein, die Sprünge, die von der Tabelle nachgeschlagen wurden, könnten direkt in den ersten Schalterblock als "Goto" -Ziele eingefügt worden sein. –

Antwort

11

Die zitieren aus dem javac source code:

  * The general approach used is to translate a single 
     * string switch statement into a series of two chained 
     * switch statements: the first a synthesized statement 
     * switching on the argument string's hash value and 
     * computing a string's position in the list of original 
     * case labels, if any, followed by a second switch on the 
     * computed integer value. The second switch has the same 
     * code structure as the original string switch statement 
     * except that the string case labels are replaced with 
     * positional integer constants starting at 0. 
     * 
     * The first switch statement can be thought of as an 
     * inlined map from strings to their position in the case 
     * label list. An alternate implementation would use an 
     * actual Map for this purpose, as done for enum switches. 
     * 
     * With some additional effort, it would be possible to 
     * use a single switch statement on the hash code of the 
     * argument, but care would need to be taken to preserve 
     * the proper control flow in the presence of hash 
     * collisions and other complications, such as 
     * fallthroughs. Switch statements with one or two 
     * alternatives could also be specially translated into 
     * if-then statements to omit the computation of the hash 
     * code.