JLS
O JLS 7 3.10.5 o define e fornece um exemplo prático:
Além disso, um literal de string sempre se refere à mesma instância da classe String. Isso ocorre porque literais de string - ou, de maneira mais geral, strings que são os valores de expressões constantes (§15.28) - são "internados" para compartilhar instâncias exclusivas, usando o método String.intern.
Exemplo 3.10.5-1. Literais de cordas
O programa que consiste na unidade de compilação (§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"; }
e a unidade de compilação:
package other;
public class Other { public static String hello = "Hello"; }
produz a saída:
true true true true false true
JVMS
O JVMS 7 5.1 diz que a internação é implementada de maneira mágica e eficiente com uma CONSTANT_String_info
estrutura dedicada (ao contrário da maioria dos outros objetos que possuem representações mais genéricas):
Um literal de string é uma referência a uma instância da classe String e é derivado de uma estrutura CONSTANT_String_info (§4.4.3) na representação binária de uma classe ou interface. A estrutura CONSTANT_String_info fornece a sequência de pontos de código Unicode que constituem a cadeia literal.
A linguagem de programação Java requer que literais de string idênticos (ou seja, literais que contenham a mesma sequência de pontos de código) se refiram à mesma instância da classe String (JLS §3.10.5). Além disso, se o método String.intern for chamado em qualquer string, o resultado será uma referência à mesma instância de classe que seria retornada se essa string aparecesse como um literal. Portanto, a seguinte expressão deve ter o valor true:
("a" + "b" + "c").intern() == "abc"
Para derivar um literal de cadeia, a Java Virtual Machine examina a sequência de pontos de código fornecidos pela estrutura CONSTANT_String_info.
Se o método String.intern já foi chamado em uma instância da classe String que contém uma sequência de pontos de código Unicode idênticos àqueles fornecidos pela estrutura CONSTANT_String_info, o resultado da derivação literal da string é uma referência à mesma instância da classe String.
Caso contrário, uma nova instância da classe String será criada contendo a sequência de pontos de código Unicode fornecidos pela estrutura CONSTANT_String_info; uma referência a essa instância de classe é o resultado da derivação literal de cadeia de caracteres. Por fim, o método interno da nova instância String é chamado.
Bytecode
Vamos descompilar alguns bytecode do OpenJDK 7 para ver a internação em ação.
Se decompilarmos:
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);
}
}
temos na piscina constante:
#2 = String #32 // abc
[...]
#32 = Utf8 abc
e 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
Observe como:
0
e 3
: a mesma ldc #2
constante é carregada (os literais)
12
: uma nova instância de string é criada (com #2
como argumento)
35
: a
e c
são comparados como objetos regulares comif_acmpne
A representação de strings constantes é bastante mágica no bytecode:
- possui uma estrutura CONSTANT_String_info dedicada , diferentemente dos objetos regulares (por exemplo
new String
)
- o struct aponta para uma estrutura CONSTANT_Utf8_info que contém os dados. Esses são os únicos dados necessários para representar a sequência.
e a citação da JVMS acima parece dizer que sempre que o Utf8 apontado é o mesmo, instâncias idênticas são carregadas ldc
.
Eu fiz testes semelhantes para campos e:
static final String s = "abc"
aponta para a tabela constante por meio do atributo ConstantValue
- campos não finais não possuem esse atributo, mas ainda podem ser inicializados com
ldc
Conclusão : existe suporte direto de bytecode para o conjunto de strings e a representação de memória é eficiente.
Bônus: compare isso ao pool Inteiro , que não possui suporte direto ao bytecode (ou seja, sem CONSTANT_String_info
analógico).