Gdy zadeklarujesz zmienną String
(która jest niezmienna ) jako final
i zainicjujesz ją wyrażeniem stałym czasu kompilacji, stanie się ono również wyrażeniem stałym czasu kompilacji, a jego wartość zostanie podkreślona przez kompilator, w którym jest używana. Zatem w drugim przykładzie kodu, po wstawieniu wartości, kompilacja łańcucha jest tłumaczona przez kompilator na:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
co w porównaniu do "string"
da ci true
, ponieważ literały łańcuchowe są internowane .
Z JLS §4.12.4 - final
Zmienne :
Zmienna pierwotnego typu lub typu String
, która jest final
inicjowana za pomocą wyrażenia stałego w czasie kompilacji (§ 15.28), nazywana jest zmienną stałą .
Również z JLS § 15.28 - Wyrażenie stałe:
Wyrażenia typu stałego w czasie kompilacji String
są zawsze „internowane”, tak aby współużytkować unikalne instancje przy użyciu tej metody String#intern()
.
Nie jest tak w pierwszym przykładzie kodu, w którym String
zmienne nie są final
. Nie są to zatem wyrażenia stałe czasu kompilacji. Operacja konkatenacji zostanie opóźniona do czasu wykonania, co spowoduje utworzenie nowego String
obiektu. Możesz to sprawdzić, porównując kod bajtowy obu kodów.
Pierwszy przykład kodu (nie w final
wersji) jest kompilowany do następującego kodu bajtowego:
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
Oczywiste jest przechowywanie str
i ing
w dwóch różnych zmiennych, a przy użyciu StringBuilder
wykonać operację konkatenacji.
Natomiast twój drugi przykład kodu ( final
wersja) wygląda następująco:
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
Więc bezpośrednio wstawia ostatnią zmienną do utworzenia ciągu string
w czasie kompilacji, który jest ładowany przez ldc
operację w kroku 0
. Następnie drugi ldc
krok literału jest ładowany przez operację w kroku 7
. Nie wymaga tworzenia żadnego nowego String
obiektu w czasie wykonywania. Łańcuch jest już znany w czasie kompilacji i jest internowany.