Wenn Sie eine String( unveränderliche ) Variable als deklarieren finalund sie mit einem Konstantenausdruck zur Kompilierungszeit initialisieren, wird sie auch zu einem Konstantenausdruck zur Kompilierungszeit, und ihr Wert wird vom Compiler, in dem sie verwendet wird, eingefügt. In Ihrem zweiten Codebeispiel wird die Zeichenfolgenverkettung nach dem Inlinieren der Werte vom Compiler in Folgendes übersetzt:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
was im Vergleich zu "string"gibt true, weil String-Literale interniert sind .
Aus JLS §4.12.4 - finalVariablen :
Eine Variable vom primitiven Typ oder Typ String, die finalmit einem konstanten Ausdruck zur Kompilierungszeit (§15.28) initialisiert wird, wird als konstante Variable bezeichnet .
Auch aus JLS §15.28 - Konstanter Ausdruck:
Konstante Ausdrücke des Typs Stringzur Kompilierungszeit werden immer "interniert" , um mithilfe der Methode eindeutige Instanzen gemeinsam zu nutzen String#intern().
Dies ist in Ihrem ersten Codebeispiel nicht der Fall, in dem die StringVariablen nicht vorhanden sind final. Sie sind also keine konstanten Ausdrücke zur Kompilierungszeit. Der dortige Verkettungsvorgang wird bis zur Laufzeit verzögert, wodurch ein neues StringObjekt erstellt wird. Sie können dies überprüfen, indem Sie den Bytecode beider Codes vergleichen.
Das erste Codebeispiel (Nichtversion final) wird mit dem folgenden Bytecode kompiliert:
Code:
0: ldc #2; //String str
2: astore_1
3: ldc #3; //String ing
5: astore_2
6: new #4; //class java/lang/StringBuilder
9: dup
10: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_3
29: ldc #9; //String string
31: if_acmpne 38
34: iconst_1
35: goto 39
38: iconst_0
39: invokevirtual #10; //Method java/io/PrintStream.println:(Z)V
42: return
Es ist klar, dass es in zwei separaten Variablen gespeichert strund ingverwendet wird StringBuilder, um die Verkettungsoperation auszuführen.
Ihr zweites Codebeispiel ( finalVersion) sieht folgendermaßen aus:
Code:
0: ldc #2; //String string
2: astore_3
3: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_3
7: ldc #2; //String string
9: if_acmpne 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokevirtual #4; //Method java/io/PrintStream.println:(Z)V
20: return
Daher wird die endgültige Variable direkt eingefügt, um stringzur Kompilierungszeit einen String zu erstellen, der durch die ldcschrittweise Operation geladen wird 0. Dann wird das zweite String-Literal durch ldcschrittweise Operation geladen 7. Zur StringLaufzeit wird kein neues Objekt erstellt. Der String ist bereits zur Kompilierungszeit bekannt und wird interniert.