JLS
JLS 7 3.10.5 lo define y da un ejemplo práctico:
Además, un literal de cadena siempre se refiere a la misma instancia de la clase String. Esto se debe a que los literales de cadena, o, más generalmente, las cadenas que son valores de expresiones constantes (§15.28), están "internados" para compartir instancias únicas, utilizando el método String.intern.
Ejemplo 3.10.5-1. Literales de cuerda
El programa que consiste en la unidad de compilación (§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"; }
y la unidad de compilación:
package other;
public class Other { public static String hello = "Hello"; }
produce la salida:
true true true true false true
JVMS
JVMS 7 5.1 dice que el internado se implementa de manera mágica y eficiente con una CONSTANT_String_info
estructura dedicada (a diferencia de la mayoría de los otros objetos que tienen representaciones más genéricas):
Un literal de cadena es una referencia a una instancia de clase String, y se deriva de una estructura CONSTANT_String_info (§4.4.3) en la representación binaria de una clase o interfaz. La estructura CONSTANT_String_info proporciona la secuencia de puntos de código Unicode que constituyen el literal de cadena.
El lenguaje de programación Java requiere que los literales de cadena idénticos (es decir, los literales que contienen la misma secuencia de puntos de código) deben referirse a la misma instancia de la clase String (JLS §3.10.5). Además, si se llama al método String.intern en cualquier cadena, el resultado es una referencia a la misma instancia de clase que se devolvería si esa cadena apareciera como un literal. Por lo tanto, la siguiente expresión debe tener el valor verdadero:
("a" + "b" + "c").intern() == "abc"
Para derivar un literal de cadena, la máquina virtual Java examina la secuencia de puntos de código dada por la estructura CONSTANT_String_info.
Si el método String.intern se ha llamado previamente en una instancia de clase String que contiene una secuencia de puntos de código Unicode idénticos a los dados por la estructura CONSTANT_String_info, entonces el resultado de la derivación literal de cadena es una referencia a esa misma instancia de clase String.
De lo contrario, se crea una nueva instancia de clase String que contiene la secuencia de puntos de código Unicode dada por la estructura CONSTANT_String_info; una referencia a esa instancia de clase es el resultado de la derivación literal de cadena. Finalmente, se invoca el método interno de la nueva instancia de String.
Bytecode
Vamos a descompilar algunos bytecode de OpenJDK 7 para ver la internación en acción.
Si descompilamos:
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);
}
}
tenemos en el grupo constante:
#2 = String #32 // abc
[...]
#32 = Utf8 abc
y 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
Tenga en cuenta cómo:
0
y 3
: ldc #2
se carga la misma constante (los literales)
12
: se crea una nueva instancia de cadena (con un #2
argumento)
35
: a
y c
se comparan como objetos normales conif_acmpne
La representación de cadenas constantes es bastante mágica en el código de bytes:
- tiene una estructura dedicada CONSTANT_String_info , a diferencia de los objetos normales (p
new String
. ej. )
- la estructura apunta a una estructura CONSTANT_Utf8_info que contiene los datos. Esos son los únicos datos necesarios para representar la cadena.
y la cita JVMS anterior parece decir que siempre que el Utf8 apuntado es el mismo, se cargan instancias idénticas ldc
.
He realizado pruebas similares para campos y:
static final String s = "abc"
apunta a la tabla constante a través del atributo ConstantValue
- los campos no finales no tienen ese atributo, pero aún se pueden inicializar con
ldc
Conclusión : existe un soporte directo de bytecode para el conjunto de cadenas y la representación de la memoria es eficiente.
Bonificación: compárelo con el grupo Integer , que no tiene soporte directo de bytecode (es decir, no CONSTANT_String_info
analógico).