Un oggetto immutabile è un oggetto in cui i campi interni (o almeno tutti i campi interni che influiscono sul suo comportamento esterno) non possono essere modificati.
Ci sono molti vantaggi nelle stringhe immutabili:
Prestazioni: eseguire la seguente operazione:
String substring = fullstring.substring(x,y);
La C sottostante per il metodo substring () è probabilmente qualcosa del genere:
// Assume string is stored like this:
struct String { char* characters; unsigned int length; };
// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
struct String* out = malloc(sizeof(struct String));
out->characters = in->characters + begin;
out->length = end - begin;
return out;
}
Nota che nessuno dei personaggi deve essere copiato! Se l'oggetto String fosse mutabile (i caratteri potrebbero cambiare in seguito), dovresti copiare tutti i caratteri, altrimenti le modifiche ai caratteri nella sottostringa verrebbero riflesse nell'altra stringa in seguito.
Concorrenza: se la struttura interna di un oggetto immutabile è valida, sarà sempre valida. Non è possibile che thread diversi possano creare uno stato non valido all'interno di quell'oggetto. Quindi, gli oggetti immutabili sono Thread Safe .
Garbage collection: è molto più facile per il garbage collector prendere decisioni logiche su oggetti immutabili.
Tuttavia, ci sono anche aspetti negativi dell'immutabilità:
Performance: Aspetta, pensavo avessi detto che la performance era un vantaggio dell'immutabilità! Beh, a volte lo è, ma non sempre. Prendi il seguente codice:
foo = foo.substring(0,4) + "a" + foo.substring(5); // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder
Le due righe sostituiscono entrambe il quarto carattere con la lettera "a". Non solo il secondo pezzo di codice è più leggibile, è anche più veloce. Guarda come dovresti fare il codice sottostante per pippo. Le sottostringhe sono facili, ma ora perché c'è già un personaggio nello spazio cinque e qualcos'altro potrebbe fare riferimento a foo, non puoi semplicemente cambiarlo; devi copiare l'intera stringa (ovviamente alcune di queste funzionalità sono astratte in funzioni nella C reale sottostante, ma il punto qui è mostrare il codice che viene eseguito tutto in un unico posto).
struct String* concatenate(struct String* first, struct String* second)
{
struct String* new = malloc(sizeof(struct String));
new->length = first->length + second->length;
new->characters = malloc(new->length);
int i;
for(i = 0; i < first->length; i++)
new->characters[i] = first->characters[i];
for(; i - first->length < second->length; i++)
new->characters[i] = second->characters[i - first->length];
return new;
}
// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));
Nota che il concatenato viene chiamato due volte, il che significa che è necessario eseguire il loop dell'intera stringa! Confronta questo con il codice C per l' bar
operazione:
bar->characters[4] = 'a';
L'operazione di stringa mutabile è ovviamente molto più veloce.
In conclusione: nella maggior parte dei casi, si desidera una stringa immutabile. Ma se devi aggiungere e inserire molte stringhe in una stringa, hai bisogno della mutabilità per la velocità. Se si desidera ottenere vantaggi in termini di sicurezza della concorrenza e garbage collection, la chiave è mantenere gli oggetti mutabili locali in un metodo:
// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
StringBuilder mutable;
boolean first = true;
for(int i = 0; i < strings.length; i++)
{
if(!first) first = false;
else mutable.append(separator);
mutable.append(strings[i]);
}
return mutable.toString();
}
Poiché l' mutable
oggetto è un riferimento locale, non devi preoccuparti della sicurezza della concorrenza (solo un thread lo tocca mai). E poiché non viene indicato in nessun altro luogo, viene allocato solo nello stack, quindi viene deallocato non appena termina la chiamata di funzione (non devi preoccuparti della garbage collection). E ottieni tutti i benefici prestazionali di mutabilità e immutabilità.