JLS
JLS 7 3.10.5 le définit et donne un exemple pratique:
De plus, un littéral de chaîne fait toujours référence à la même instance de la classe String. En effet, les littéraux de chaîne - ou, plus généralement, les chaînes qui sont les valeurs d'expressions constantes (§15.28) - sont "internés" afin de partager des instances uniques, en utilisant la méthode String.intern.
Exemple 3.10.5-1. Littéraux de chaîne
Le programme composé de l'unité de compilation (§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"; }
et l'unité de compilation:
package other;
public class Other { public static String hello = "Hello"; }
produit la sortie:
true true true true false true
JVMS
JVMS 7 5.1 dit que l'internement est implémenté de manière magique et efficace avec une CONSTANT_String_info
structure dédiée (contrairement à la plupart des autres objets qui ont des représentations plus génériques):
Un littéral de chaîne est une référence à une instance de la classe String et est dérivé d'une structure CONSTANT_String_info (§4.4.3) dans la représentation binaire d'une classe ou d'une interface. La structure CONSTANT_String_info donne la séquence de points de code Unicode constituant le littéral chaîne.
Le langage de programmation Java requiert que des littéraux de chaîne identiques (c'est-à-dire des littéraux qui contiennent la même séquence de points de code) doivent faire référence à la même instance de classe String (JLS §3.10.5). De plus, si la méthode String.intern est appelée sur n'importe quelle chaîne, le résultat est une référence à la même instance de classe qui serait retournée si cette chaîne apparaissait comme un littéral. Ainsi, l'expression suivante doit avoir la valeur true:
("a" + "b" + "c").intern() == "abc"
Pour dériver un littéral de chaîne, la machine virtuelle Java examine la séquence de points de code donnée par la structure CONSTANT_String_info.
Si la méthode String.intern a déjà été appelée sur une instance de classe String contenant une séquence de points de code Unicode identique à celle donnée par la structure CONSTANT_String_info, le résultat de la dérivation littérale de chaîne est une référence à cette même instance de classe String.
Sinon, une nouvelle instance de la classe String est créée contenant la séquence de points de code Unicode donnée par la structure CONSTANT_String_info; une référence à cette instance de classe est le résultat d'une dérivation littérale de chaîne. Enfin, la méthode interne de la nouvelle instance de String est invoquée.
Bytecode
Décompilons du bytecode OpenJDK 7 pour voir l'internement en action.
Si nous décompilons:
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);
}
}
nous avons sur le bassin constant:
#2 = String #32 // abc
[...]
#32 = Utf8 abc
et 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
Notez comment:
0
et 3
: la même ldc #2
constante est chargée (les littéraux)
12
: une nouvelle instance de chaîne est créée (avec #2
comme argument)
35
: a
et c
sont comparés comme des objets normaux avecif_acmpne
La représentation des chaînes constantes est assez magique sur le bytecode:
- il a une structure CONSTANT_String_info dédiée , contrairement aux objets réguliers (par exemple
new String
)
- la structure pointe vers une structure CONSTANT_Utf8_info qui contient les données. Ce sont les seules données nécessaires pour représenter la chaîne.
et la citation JVMS ci-dessus semble dire que chaque fois que l'Utf8 pointé est le même, des instances identiques sont chargées par ldc
.
J'ai fait des tests similaires pour les champs et:
static final String s = "abc"
pointe vers la table des constantes via l' attribut ConstantValue
- les champs non finaux n'ont pas cet attribut, mais peuvent toujours être initialisés avec
ldc
Conclusion : le pool de chaînes prend directement en charge le bytecode et la représentation en mémoire est efficace.
Bonus: comparez cela au pool Integer , qui n'a pas de prise en charge directe du bytecode (c'est-à-dire pas d' CONSTANT_String_info
analogue).