JLS
JLS 7 3.10.5 definiuje to i podaje praktyczny przykład:
Co więcej, literał łańcuchowy zawsze odnosi się do tego samego wystąpienia klasy String. Wynika to z tego, że literały łańcuchowe - lub, bardziej ogólnie, łańcuchy, które są wartościami wyrażeń stałych (§ 15.28) - są „internowane”, aby dzielić unikalne instancje, przy użyciu metody String.intern.
Przykład 3.10.5-1. Literały smyczkowe
Program składający się z jednostki kompilacyjnej (§7.3):
package testPackage;
class Test {
public static void main(String[] args) {
String hello = "Hello", lo = "lo";
System.out.print((hello == "Hello") + " ");
System.out.print((Other.hello == hello) + " ");
System.out.print((other.Other.hello == hello) + " ");
System.out.print((hello == ("Hel"+"lo")) + " ");
System.out.print((hello == ("Hel"+lo)) + " ");
System.out.println(hello == ("Hel"+lo).intern());
}
}
class Other { static String hello = "Hello"; }
oraz jednostka kompilacyjna:
package other;
public class Other { public static String hello = "Hello"; }
daje wynik:
true true true true false true
JVMS
JVMS 7 5.1 mówi , że internowanie jest realizowane magicznie i skutecznie za pomocą dedykowanej CONSTANT_String_info
struktury (w przeciwieństwie do większości innych obiektów, które mają bardziej ogólne reprezentacje):
Literał łańcuchowy jest odwołaniem do instancji klasy Łańcuch i pochodzi ze struktury CONSTANT_String_info (§4.4.3) w binarnej reprezentacji klasy lub interfejsu. Struktura CONSTANT_String_info podaje sekwencję punktów kodowych Unicode stanowiących literał ciągu.
Język programowania Java wymaga, aby identyczne literały łańcuchowe (to znaczy literały zawierające tę samą sekwencję punktów kodowych) musiały odnosić się do tej samej instancji klasy String (JLS §3.10.5). Ponadto, jeśli metoda String.intern zostanie wywołana na dowolnym ciągu, wynikiem jest odwołanie do tej samej instancji klasy, która zostałaby zwrócona, gdyby ten ciąg pojawił się jako literał. Dlatego poniższe wyrażenie musi mieć wartość true:
("a" + "b" + "c").intern() == "abc"
Aby uzyskać literał ciąg, wirtualna maszyna Java sprawdza sekwencję punktów kodu podaną przez strukturę CONSTANT_String_info.
Jeśli metoda String.intern została wcześniej wywołana na instancji klasy String zawierającej sekwencję punktów kodu Unicode identycznych z podanymi przez strukturę CONSTANT_String_info, to wynik wyprowadzenia literału łańcucha jest odniesieniem do tej samej instancji klasy String.
W przeciwnym razie tworzona jest nowa instancja klasy String zawierająca sekwencję punktów kodu Unicode podaną przez strukturę CONSTANT_String_info; odwołanie do tej instancji klasy jest wynikiem pochodnej literału łańcuchowego. Na koniec wywoływana jest metoda intern nowej instancji String.
Kod bajtowy
Dekompilujmy kod bajtowy OpenJDK 7, aby zobaczyć internowanie w akcji.
Jeśli dekompilujemy:
public class StringPool {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a);
System.out.println(b);
System.out.println(a == c);
}
}
mamy na stałej puli:
#2 = String #32 // abc
[...]
#32 = Utf8 abc
i main
:
0: ldc #2 // String abc
2: astore_1
3: ldc #2 // String abc
5: astore_2
6: new #3 // class java/lang/String
9: dup
10: ldc #2 // String abc
12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
Uwaga jak:
0
i 3
: ldc #2
ładowana jest ta sama stała (literały)
12
: tworzona jest nowa instancja ciągu (z #2
argumentem jako)
35
: a
i c
są porównywane jako zwykłe obiekty zif_acmpne
Reprezentacja ciągów ciągłych jest dość magiczna w kodzie bajtowym:
a powyższy cytat JVMS wydaje się mówić, że ilekroć wskazany Utf8 jest taki sam, ładowane są identyczne instancje ldc
.
Zrobiłem podobne testy dla pól i:
static final String s = "abc"
wskazuje na stałą tabelę poprzez atrybut ConstantValue
- pola nie-końcowe nie mają tego atrybutu, ale nadal można je zainicjować za pomocą
ldc
Wniosek : istnieje bezpośrednia obsługa kodów bajtów dla puli ciągów, a reprezentacja pamięci jest wydajna.
Bonus: porównaj to z pulą liczb całkowitych , która nie ma bezpośredniego wsparcia dla kodu bajtowego (tzn. Nie ma CONSTANT_String_info
analogu).