JLS
JLS 7 3.10.5 lo definisce e fornisce un esempio pratico:
Inoltre, un letterale stringa si riferisce sempre alla stessa istanza della classe String. Questo perché i letterali di stringa - o, più in generale, stringhe che sono i valori di espressioni costanti (§15.28) - vengono "internati" in modo da condividere istanze univoche, usando il metodo String.intern.
Esempio 3.10.5-1. Letterali per archi
Il programma costituito dall'unità di compilazione (§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 l'unità di compilazione:
package other;
public class Other { public static String hello = "Hello"; }
produce l'output:
true true true true false true
JVM
JVMS 7 5.1 afferma che il interning viene implementato magicamente ed efficientemente con una CONSTANT_String_info
struttura dedicata (a differenza della maggior parte degli altri oggetti che hanno rappresentazioni più generiche):
Un letterale stringa è un riferimento a un'istanza della classe String ed è derivato da una struttura CONSTANT_String_info (§4.4.3) nella rappresentazione binaria di una classe o interfaccia. La struttura CONSTANT_String_info fornisce la sequenza di punti di codice Unicode che costituiscono la stringa letterale.
Il linguaggio di programmazione Java richiede che i letterali stringa identici (ovvero i letterali che contengono la stessa sequenza di punti di codice) debbano fare riferimento alla stessa istanza della classe String (JLS §3.10.5). Inoltre, se il metodo String.intern viene chiamato su qualsiasi stringa, il risultato è un riferimento alla stessa istanza della classe che verrebbe restituita se quella stringa apparisse come un valore letterale. Pertanto, la seguente espressione deve avere il valore vero:
("a" + "b" + "c").intern() == "abc"
Per derivare letteralmente una stringa, la Java Virtual Machine esamina la sequenza di punti di codice forniti dalla struttura CONSTANT_String_info.
Se il metodo String.intern è stato precedentemente chiamato su un'istanza della classe String contenente una sequenza di punti di codice Unicode identici a quelli forniti dalla struttura CONSTANT_String_info, il risultato della derivazione letterale di stringa è un riferimento a quella stessa istanza della classe String.
Altrimenti, viene creata una nuova istanza della classe String contenente la sequenza di punti di codice Unicode forniti dalla struttura CONSTANT_String_info; un riferimento a quell'istanza di classe è il risultato della derivazione letterale di stringa. Infine, viene invocato il metodo intern della nuova istanza String.
bytecode
Decompiliamo alcuni bytecode di OpenJDK 7 per vedere lo interning in azione.
Se decompiliamo:
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);
}
}
abbiamo nel pool costante:
#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
Nota come:
0
e 3
: ldc #2
viene caricata la stessa costante (i letterali)
12
: viene creata una nuova istanza di stringa (con #2
come argomento)
35
: a
e c
vengono confrontati come oggetti regolari conif_acmpne
La rappresentazione di stringhe costanti è piuttosto magica sul bytecode:
- ha una struttura CONSTANT_String_info dedicata , a differenza degli oggetti normali (ad es.
new String
)
- la struttura punta a una CONSTANT_Utf8_info struttura che contiene i dati. Questi sono gli unici dati necessari per rappresentare la stringa.
e la citazione JVMS sopra sembra dire che ogni volta che Utf8 indicato è lo stesso, vengono caricate istanze identiche ldc
.
Ho fatto test simili per i campi e:
static final String s = "abc"
punta alla tabella costante tramite l' attributo ConstantValue
- i campi non finali non hanno quell'attributo, ma possono comunque essere inizializzati con
ldc
Conclusione : esiste un supporto bytecode diretto per il pool di stringhe e la rappresentazione della memoria è efficiente.
Bonus: confrontalo con il pool Integer , che non ha il supporto bytecode diretto (cioè nessun CONSTANT_String_info
analogo).