Quanti oggetti String verranno creati

Ho il seguente codice Java:

public String makinStrings() { String s = "Fred"; s = s + "47"; s = s.substring(2, 5); s = s.toUpperCase(); return s.toString(); } 

La domanda è in qualche modo semplice: quanti oggetti String verranno creati quando viene invocato questo metodo?

All’inizio ho risposto che sono stati creati 5 oggetti String, ma la risposta del mio libro dice che sono stati creati solo 3 oggetti e non è stata fornita alcuna spiegazione (questa è una domanda SCJP).

Dal mio punto di vista ci sono 5 oggetti: “Fred”, “47”, “Fred47”, “ed4”, “ED4”.

Ho anche trovato questa domanda su un esame di simulazione SCJP, con la stessa risposta 3.

“Fred” e “47” verranno dalla serie letterale di stringhe. In quanto tali , non verranno creati quando viene richiamato il metodo. Invece saranno messi lì quando la class viene caricata (o prima, se altre classi usano costanti con lo stesso valore).

“Fred47”, “ed4” e “ED4” sono gli oggetti a 3 String che verranno creati su ogni invocazione di metodo.

I programmi tendono a contenere molti valori letterali stringa nel loro codice. In Java, queste costanti sono raccolte in qualcosa chiamato la tabella di stringhe per l’efficienza. Ad esempio, se si utilizza la stringa "Name: " in dieci posizioni diverse, la JVM (in genere) ha solo un’istanza di quella stringa e in tutte le dieci posizioni in cui viene utilizzata, i riferimenti puntano tutti a quell’istanza. Ciò consente di risparmiare memoria.

Questa ottimizzazione è ansible perché String è immutabile . Se fosse ansible cambiare una stringa, cambiarla di un posto significherebbe che cambi anche negli altri nove. Ecco perché qualsiasi operazione che modifica una stringa restituisce una nuova istanza. Ecco perché se lo fai:

 String s = "boink"; s.toUpperCase(); System.out.println(s); 

stampa BOINK , non BOINK .

Ora c’è un altro inganno: più istanze di java.lang.String potrebbero puntare allo stesso char[] sottostante char[] per i loro dati di carattere, in altre parole, potrebbero essere viste differenti sullo stesso char[] , usando solo una slice dell’array. Di nuovo, un’ottimizzazione per l’efficienza. Il metodo substring() è uno dei casi in cui ciò accade.

 s1 = "Fred47"; //String s1: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=0, length=6 // ^........................^ s2 = s1.substring(2, 5); //String s2: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=2, length=3 // ^.........^ // the two strings are sharing the same char[]! 

Nella tua domanda SCJP, tutto questo si riduce a:

  • La stringa "Fred" è presa dalla tabella String.
  • La stringa "47" viene ricavata dalla tabella String.
  • La stringa "Fred47" viene creata durante la chiamata al metodo. // 1
  • La stringa "ed4" viene creata durante la chiamata al metodo, condividendo lo stesso array di supporto di "Fred47" // 2
  • La stringa "ED4" viene creata durante la chiamata al metodo. // 3
  • s.toString() non ne crea uno nuovo, ma lo restituisce.

Un caso interessante di tutto questo: considera cosa succede se hai una stringa molto lunga, ad esempio una pagina web presa da Internet, diciamo che la lunghezza del char[] è di due megabyte. Se si prende la substring(0, 4) di questa, si ottiene una nuova stringa che sembra essere lunga solo quattro caratteri, ma condivide ancora quei due megabyte di dati di supporto. Questo non è tutto ciò che è comune nel mondo reale, ma può essere un enorme spreco di memoria! Nel (raro) caso in cui ci si new String(hugeString.substring(0, 4)) in questo problema, è ansible utilizzare la new String(hugeString.substring(0, 4)) per creare una stringa con un nuovo array di supporto di dimensioni ridotte.

Infine, è ansible forzare una stringa nella tabella delle stringhe in fase di esecuzione chiamando intern() su di essa. La regola di base in questo caso: non farlo. La regola estesa: non farlo a meno che non hai usato un profiler di memoria per accertarti che si tratta di un’ottimizzazione utile.

In base all’output di javap, sembra che durante la concatenazione venga creato uno StringBuilder, non una stringa. Esistono quindi tre stringhe chiamate substring (), toUpperCase () e toString ().

L’ultima chiamata non è ridondante perché trasforma StringBuilder in una stringa.

 >javap -c Test Compiled from "Test.java" public java.lang.String makinStrings(); Code: 0: ldc #5; //String Fred 2: astore_1 3: new #6; //class java/lang/StringBuilder 6: dup 7: invokespecial #7; //Method java/lang/StringBuilder."":()V 10: aload_1 11: invokevirtual #8; //Method java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #9; //String 47 16: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_1 23: aload_1 24: iconst_2 25: iconst_5 26: invokevirtual #11; //Method java/lang/String.substring:(II)Ljava/lang/String; 29: astore_1 30: aload_1 31: invokevirtual #12; //Method java/lang/String.toUpperCase:()Ljava/lang/String; 34: astore_1 35: aload_1 36: invokevirtual #13; //Method java/lang/String.toString:()Ljava/lang/String; 39: areturn 

}