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; ссылка на этот экземпляр класса является результатом строкового литерала. Наконец, метод intern нового экземпляра String вызывается.
Bytecode
Давайте декомпилируем некоторый байт-код 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
аргументом as)
35
: a
и c
сравниваются как обычные объекты сif_acmpne
Представление константных строк довольно волшебно в байт-коде:
- у него есть специальная структура CONSTANT_String_info , в отличие от обычных объектов (например
new String
)
- структура указывает на структуру CONSTANT_Utf8_info, которая содержит данные. Это единственные необходимые данные для представления строки.
и приведенная выше цитата JVMS, кажется, говорит, что всякий раз, когда Utf8, на который указывают, является тем же самым, тогда идентичные экземпляры загружаются ldc
.
Я сделал аналогичные тесты для полей, и:
static final String s = "abc"
указывает на таблицу констант через атрибут ConstantValue
- не финальные поля не имеют этого атрибута, но все еще могут быть инициализированы с
ldc
Вывод : есть прямая поддержка байт-кода для пула строк, и представление памяти эффективно.
Бонус: сравните это с целочисленным пулом , который не имеет прямой поддержки байт-кода (т.е. не имеет CONSTANT_String_info
аналогов).