Wenn Sie eine String
( unveränderliche ) Variable als deklarieren final
und 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 - final
Variablen :
Eine Variable vom primitiven Typ oder Typ String
, die final
mit 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 String
zur 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 String
Variablen 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 String
Objekt 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 str
und ing
verwendet wird StringBuilder
, um die Verkettungsoperation auszuführen.
Ihr zweites Codebeispiel ( final
Version) 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 string
zur Kompilierungszeit einen String zu erstellen, der durch die ldc
schrittweise Operation geladen wird 0
. Dann wird das zweite String-Literal durch ldc
schrittweise Operation geladen 7
. Zur String
Laufzeit wird kein neues Objekt erstellt. Der String ist bereits zur Kompilierungszeit bekannt und wird interniert.