JLS
JLS 7 3.10.5 визначає це і дає практичний приклад:
Більше того, рядковий літерал завжди посилається на один і той же екземпляр класу String. Це тому, що рядкові літерали - або, загалом, рядки, які є значеннями постійних виразів (§15.28) - "інтерновані", щоб поділитись унікальними екземплярами, використовуючи метод String.intern.
Приклад 3.10.5-1. Строкові літерали
Програма, що складається з компіляційного блоку (§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"; }
і компіляційний блок:
package other;
public class Other { public static String hello = "Hello"; }
виробляє вихід:
true true true true false true
JVMS
JVMS 7 5.1 говорить, що інтернування реалізується магічно та ефективно з виділеною CONSTANT_String_info
структурою (на відміну від більшості інших об'єктів, які мають більш загальні уявлення):
Строковий літерал - це посилання на екземпляр класу String і походить від CONSTANT_String_info структури (§4.4.3) у двійковому поданні класу або інтерфейсу. Структура CONSTANT_String_info дає послідовність точок коду Unicode, що складають рядковий літерал.
Мова програмування Java вимагає, щоб однакові рядкові літерали (тобто літерали, що містять однакову послідовність точок коду), повинні посилатися на той самий екземпляр класу String (JLS §3.10.5). Крім того, якщо метод String.intern викликається в будь-якому рядку, результат є посиланням на той самий екземпляр класу, який буде повернуто, якщо ця рядок з'явилася як буквальна. Таким чином, наступний вираз повинен мати значення true:
("a" + "b" + "c").intern() == "abc"
Щоб отримати рядковий літерал, віртуальна машина Java вивчає послідовність кодів, заданих структурою CONSTANT_String_info.
Якщо раніше метод String.intern був викликаний в екземплярі класу String, що містить послідовність точок коду Unicode, ідентичну тій, що задана структурою CONSTANT_String_info, то результатом рядкового виведення рядка є посилання на той самий екземпляр класу String.
Інакше створюється новий екземпляр класу String, що містить послідовність точок коду Unicode, заданих структурою CONSTANT_String_info; посилання на цей екземпляр класу є результатом рядкового буквеного виведення. Нарешті, використовується інтерн-метод нового екземпляра String.
Байт-код
Давайте декомпілюємо деякий байт-код OpenJDK 7, щоб побачити стажування в дії.
Якщо ми декомпілюємо:
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);
}
}
у нас на постійному басейні:
#2 = String #32 // abc
[...]
#32 = Utf8 abc
і 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
Зверніть увагу, як:
0
і 3
: однакова ldc #2
константа завантажена (буквали)
12
: створюється новий екземпляр рядка (з #2
аргументом)
35
: a
і c
порівнюються із звичайними об'єктамиif_acmpne
Представлення постійних рядків є досить магічним у байт-коді:
а цитата JVMS вище, схоже, говорить про те, що всякий раз, коли вказано на Utf8, є однаковим, тоді завантажуються однакові екземпляри ldc
.
Я зробив подібні тести для полів, і:
static final String s = "abc"
вказує на постійну таблицю через атрибут ConstantValue
- не завершальні поля не мають цього атрибуту, але все ще можуть бути ініціалізовані
ldc
Висновок : існує пряма підтримка байт-коду для пулу рядків, і представлення пам'яті є ефективним.
Бонус: порівняйте це з пулом Integer , який не підтримує прямий байт-код (тобто немає CONSTANT_String_info
аналога).