JLS
Концепція 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 говорить :
Строковий літерал - це посилання на екземпляр класу 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
аналога).