Perché hanno deciso di renderli String
immutabili in Java e .NET (e in alcune altre lingue)? Perché non l'hanno reso mutevole?
String
è effettivamente mutabile internamente. StringBuilder
in .NET 2.0 muta una stringa . Lo lascio qui.
Perché hanno deciso di renderli String
immutabili in Java e .NET (e in alcune altre lingue)? Perché non l'hanno reso mutevole?
String
è effettivamente mutabile internamente. StringBuilder
in .NET 2.0 muta una stringa . Lo lascio qui.
Risposte:
Secondo Effective Java , capitolo 4, pagina 73, 2a edizione:
"Ci sono molte buone ragioni per questo: le classi immutabili sono più facili da progettare, implementare e utilizzare rispetto alle classi mutabili. Sono meno soggette a errori e sono più sicure.
[...]
"Gli oggetti immutabili sono semplici. Un oggetto immutabile può trovarsi esattamente in uno stato, lo stato in cui è stato creato. Se ti assicuri che tutti i costruttori stabiliscano invarianti di classe, allora è garantito che questi invarianti rimarranno veri per sempre, con nessuno sforzo da parte tua.
[...]
Gli oggetti immutabili sono intrinsecamente sicuri per i thread; non richiedono sincronizzazione. Non possono essere danneggiati da più thread che accedono ad essi contemporaneamente. Questo è di gran lunga l'approccio più semplice per raggiungere la sicurezza del filo. In effetti, nessun thread può mai osservare alcun effetto di un altro thread su un oggetto immutabile. Pertanto, gli oggetti immutabili possono essere condivisi liberamente
[...]
Altri piccoli punti dello stesso capitolo:
Non solo puoi condividere oggetti immutabili, ma puoi condividere i loro interni.
[...]
Gli oggetti immutabili sono grandi elementi costitutivi di altri oggetti, sia mutabili che immutabili.
[...]
L'unico vero svantaggio delle classi immutabili è che richiedono un oggetto separato per ciascun valore distinto.
report2.Text = report1.Text;
. Poi, da qualche altra parte, modificando il testo: report2.Text.Replace(someWord, someOtherWord);
. Ciò cambierebbe il primo rapporto e il secondo.
Ci sono almeno due ragioni.
Primo: sicurezza http://www.javafaq.nu/java-article1060.html
Il motivo principale per cui String ha reso immutabile era la sicurezza. Guarda questo esempio: abbiamo un metodo di apertura file con controllo di accesso. Passiamo una stringa a questo metodo per elaborare l'autenticazione necessaria prima che la chiamata venga passata al sistema operativo. Se String era mutabile, in qualche modo era possibile modificarne il contenuto dopo il controllo di autenticazione prima che il sistema operativo ricevesse la richiesta dal programma, quindi è possibile richiedere qualsiasi file. Quindi se hai il diritto di aprire il file di testo nella directory dell'utente ma poi al volo quando in qualche modo riesci a cambiare il nome del file, puoi richiedere di aprire il file "passwd" o qualsiasi altro. Quindi un file può essere modificato e sarà possibile accedere direttamente al sistema operativo.
Secondo: efficienza della memoria http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html
JVM mantiene internamente il "String Pool". Per raggiungere l'efficienza della memoria, JVM farà riferimento all'oggetto String dal pool. Non creerà i nuovi oggetti String. Quindi, ogni volta che crei una nuova stringa letterale, JVM verificherà nel pool se esiste già o meno. Se già presente nel pool, è sufficiente fornire il riferimento allo stesso oggetto o creare il nuovo oggetto nel pool. Ci saranno molti riferimenti che rimandano agli stessi oggetti String, se qualcuno cambia il valore, influenzerà tutti i riferimenti. Quindi, il sole ha deciso di renderlo immutabile.
In realtà, i motivi per cui le stringhe sono immutabili in Java non hanno molto a che fare con la sicurezza. I due motivi principali sono i seguenti:
Le stringhe sono un tipo di oggetto estremamente diffuso. È quindi più o meno garantito l'utilizzo in un ambiente multi-thread. Le stringhe sono immutabili per assicurarsi che sia sicuro condividere le stringhe tra i thread. La presenza di stringhe immutabili garantisce che quando si passano le stringhe dal thread A a un altro thread B, il thread B non può modificare inaspettatamente la stringa del thread A.
Questo non solo aiuta a semplificare il già complicato compito della programmazione multi-thread, ma aiuta anche con le prestazioni delle applicazioni multi-thread. L'accesso agli oggetti mutabili deve in qualche modo essere sincronizzato quando è possibile accedervi da più thread, per assicurarsi che un thread non tenti di leggere il valore dell'oggetto mentre viene modificato da un altro thread. La corretta sincronizzazione è sia difficile da eseguire correttamente per il programmatore, sia costosa in fase di esecuzione. Gli oggetti immutabili non possono essere modificati e quindi non necessitano di sincronizzazione.
Sebbene sia stato menzionato l'International String, rappresenta solo un piccolo guadagno in termini di efficienza della memoria per i programmi Java. Solo i letterali stringa sono internati. Ciò significa che solo le stringhe uguali nel codice sorgente condivideranno lo stesso oggetto String. Se il tuo programma crea dinamicamente stringhe uguali, queste saranno rappresentate in oggetti diversi.
Ancora più importante, le stringhe immutabili consentono loro di condividere i propri dati interni. Per molte operazioni sulle stringhe, ciò significa che non è necessario copiare l'array di caratteri sottostante. Ad esempio, supponiamo che tu voglia prendere i primi cinque caratteri di String. In Java, chiameresti myString.substring (0,5). In questo caso, ciò che fa il metodo substring () è semplicemente creare un nuovo oggetto String che condivide il carattere sottostante [] di myString ma che sappia che inizia con l'indice 0 e finisce con l'indice 5 di quel carattere []. Per dirlo in forma grafica, si dovrebbe finire con il seguente:
| myString |
v v
"The quick brown fox jumps over the lazy dog" <-- shared char[]
^ ^
| | myString.substring(0,5)
Questo rende questo tipo di operazioni estremamente economico e O (1) poiché l'operazione non dipende né dalla lunghezza della stringa originale, né dalla lunghezza della sottostringa che dobbiamo estrarre. Questo comportamento ha anche alcuni vantaggi di memoria, poiché molte stringhe possono condividere il loro carattere sottostante [].
char[]
è una decisione di progettazione piuttosto discutibile. Se leggi un intero file in una singola stringa e mantieni un riferimento a una sottostringa di 1 carattere, l'intero file dovrà essere tenuto in memoria.
String.substring()
esegue una copia completa, al fine di prevenire i problemi menzionati nei commenti sopra. In Java 8, i due campi che consentono la char[]
condivisione, ovvero count
e offset
, vengono rimossi, riducendo così l'impronta di memoria delle istanze di String.
Sicurezza e prestazioni del filo. Se una stringa non può essere modificata, è sicuro e veloce passare un riferimento tra più thread. Se le stringhe fossero mutabili, dovresti sempre copiare tutti i byte della stringa in una nuova istanza o fornire la sincronizzazione. Un'applicazione tipica leggerà una stringa 100 volte per ogni volta che la stringa deve essere modificata. Vedi Wikipedia sull'immutabilità .
Si dovrebbe davvero chiedere "perché X dovrebbe essere mutabile?" È meglio passare all'impostazione predefinita per l'immutabilità, a causa dei vantaggi già menzionati da Princess Fluff . Dovrebbe essere un'eccezione che qualcosa sia mutabile.
Sfortunatamente, la maggior parte degli attuali linguaggi di programmazione ha come impostazione predefinita la mutabilità, ma si spera che in futuro l'impostazione predefinita riguardi più l'immutabilità (consultare una Wish List per il prossimo linguaggio di programmazione Mainstream ).
Wow! Non riesco a credere alla disinformazione qui. String
essere immutabili non ha nulla di sicuro. Se qualcuno ha già accesso agli oggetti in un'applicazione in esecuzione (cosa che dovrebbe essere ipotizzata se stai cercando di proteggerti da qualcuno che "hackera" un String
nella tua app), sarebbero certamente molte altre opportunità disponibili per l'hacking.
È un'idea abbastanza nuova che l'immutabilità di String
affrontare i problemi di threading. Hmmm ... Ho un oggetto che viene modificato da due thread diversi. Come lo risolvo? sincronizzare l'accesso all'oggetto? Naawww ... non permettiamo a nessuno di cambiare affatto l'oggetto - questo risolverà tutti i nostri problemi di concorrenza disordinati! In effetti, rendiamo immutabili tutti gli oggetti, e quindi possiamo rimuovere il contratto sincronizzato dal linguaggio Java.
Il vero motivo (sottolineato da altri sopra) è l'ottimizzazione della memoria. È abbastanza comune in qualsiasi applicazione che venga utilizzato ripetutamente lo stesso valore letterale di stringa. È così comune, infatti, che decenni fa molti compilatori hanno ottimizzato la memorizzazione di una sola istanza String
letterale. Lo svantaggio di questa ottimizzazione è che il codice di runtime che modifica un valore String
letterale introduce un problema perché sta modificando l'istanza per tutto l'altro codice che lo condivide. Ad esempio, non sarebbe utile che una funzione da qualche parte in un'applicazione cambi il valore String
letterale "dog"
in "cat"
. A printf("dog")
si tradurrebbe in letterali (cioè, li renderebbe immutabili). Alcuni compilatori (con il supporto del sistema operativo) lo farebbero posizionando"cat"
scrittura su stdout. Per tale motivo, doveva esserci un modo per proteggersi dal codice che tenta di cambiareString
String
letterale in uno speciale segmento di memoria di sola lettura che causerebbe un errore di memoria in caso di tentativo di scrittura.
In Java questo è noto come interning. Il compilatore Java qui sta solo seguendo un'ottimizzazione della memoria standard fatta dai compilatori per decenni. E per affrontare lo stesso problema di questi String
letterali che vengono modificati in fase di esecuzione, Java rende semplicemente String
immutabile la classe (cioè, non ti dà setter che ti permetterebbero di cambiare il String
contenuto). String
s non dovrebbe essere immutabile se String
non si verificasse l' internamento dei letterali.
String
e StringBuffer
, ma sfortunatamente pochi altri tipi seguono quel modello.
String
non è un tipo primitivo, ma normalmente si desidera utilizzarlo con la semantica del valore, ovvero come un valore.
Un valore è qualcosa di cui ti puoi fidare non cambierà alle tue spalle. Se scrivi: String str = someExpr();
non vuoi che cambi a meno che tu non faccia qualcosa con str
.
String
poiché Object
ha una semantica puntatore naturale, per ottenere valore anche la semantica deve essere immutabile.
Un fattore è che, se String
fossero mutabili, gli oggetti che li archiviano String
dovrebbero stare attenti a conservare le copie, per evitare che i loro dati interni cambino senza preavviso. Dato che gli String
s sono un tipo abbastanza primitivo come i numeri, è bello quando si possono trattarli come se fossero passati per valore, anche se sono passati per riferimento (che aiuta anche a risparmiare memoria).
So che questo è un bernoccolo, ma ... Sono davvero immutabili? Considera quanto segue.
public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
fixed (char* ptr = s)
{
*((char*)(ptr + i)) = c;
}
}
...
string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3
Potresti persino renderlo un metodo di estensione.
public static class Extensions
{
public static unsafe void MutableReplaceIndex(this string s, char c, int i)
{
fixed (char* ptr = s)
{
*((char*)(ptr + i)) = c;
}
}
}
Il che rende il seguente lavoro
s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);
Conclusione: sono in uno stato immutabile noto al compilatore. Di solito quanto sopra si applica solo alle stringhe .NET poiché Java non ha puntatori. Tuttavia, una stringa può essere completamente mutabile utilizzando i puntatori in C #. Non è il modo in cui i puntatori devono essere utilizzati, utilizzati in modo pratico o utilizzati in modo sicuro; è comunque possibile, piegando così l'intera regola "mutabile". Normalmente non è possibile modificare direttamente un indice di una stringa e questo è l'unico modo. Esiste un modo per impedire ciò impedendo le istanze del puntatore di stringhe o eseguendo una copia quando viene indicata una stringa, ma nessuna delle due viene eseguita, il che rende le stringhe in C # non del tutto immutabili.
Per la maggior parte degli scopi, una "stringa" è (usata / trattata come / pensata / presunta) un'unità atomica significativa , proprio come un numero .
Dovresti sapere perché. Basta pensarci.
Odio dirlo, ma sfortunatamente ne stiamo discutendo perché il nostro linguaggio fa schifo e stiamo provando a usare una sola parola, stringa , per descrivere un concetto complesso o una classe di oggetti contestualmente situati.
Eseguiamo calcoli e confronti con "stringhe" simili a come facciamo con i numeri. Se le stringhe (o numeri interi) fossero mutabili, dovremmo scrivere un codice speciale per bloccare i loro valori in forme locali immutabili al fine di eseguire qualsiasi tipo di calcolo in modo affidabile. Pertanto, è meglio pensare a una stringa come un identificatore numerico, ma invece di essere lunga 16, 32 o 64 bit, potrebbe essere lunga centinaia di bit.
Quando qualcuno dice "stringa", pensiamo tutti a cose diverse. Coloro che la considerano semplicemente come un insieme di personaggi, senza uno scopo particolare in mente, saranno ovviamente sconvolti dal fatto che qualcuno abbia appena deciso che non dovrebbero essere in grado di manipolare quei personaggi. Ma la classe "stringa" non è solo una matrice di caratteri. È un STRING
, non un char[]
. Ci sono alcune ipotesi di base sul concetto che chiamiamo "stringa", e generalmente possono essere descritte come unità atomiche significative di dati codificati come un numero. Quando le persone parlano di "manipolazione di stringhe", forse stanno davvero parlando di manipolazione di personaggi per costruire stringhe , e StringBuilder è ottimo per questo.
Considera per un momento come sarebbe se le stringhe fossero mutabili. La seguente funzione API potrebbe essere indotto a ritornare le informazioni per un altro utente se il mutevole stringa username è intenzionalmente o modificato da un altro filo quando questa funzione lo utilizza:
string GetPersonalInfo( string username, string password )
{
string stored_password = DBQuery.GetPasswordFor( username );
if (password == stored_password)
{
//another thread modifies the mutable 'username' string
return DBQuery.GetPersonalInfoFor( username );
}
}
La sicurezza non riguarda solo il "controllo degli accessi", ma anche "sicurezza" e "garanzia di correttezza". Se un metodo non può essere facilmente scritto e dipende da eseguire un semplice calcolo o confronto in modo affidabile, allora non è sicuro chiamarlo, ma sarebbe sicuro rimettere in discussione il linguaggio di programmazione stesso.
unsafe
) o semplicemente attraverso la riflessione (puoi ottenere facilmente il campo sottostante). Questo annulla il punto sulla sicurezza, come chiunque voglia intenzionalmente cambiare una stringa, può farlo abbastanza facilmente. Tuttavia, fornisce sicurezza ai programmatori: a meno che tu non faccia qualcosa di speciale, la stringa è garantita immutabile (ma non è thread-safe!).
L'immutabilità non è così strettamente legata alla sicurezza. Per questo, almeno in .NET, ottieni la SecureString
classe.
Modifica successiva: in Java troverai GuardedString
un'implementazione simile.
La decisione di rendere mutevole la stringa in C ++ causa molti problemi, vedi questo eccellente articolo di Kelvin Henney sulla Mad COW Disease .
COW = Copia in scrittura.
È un compromesso. String
vanno nel String
pool e quando crei più messaggi identici String
condividono la stessa memoria. I progettisti hanno pensato che questa tecnica di salvataggio della memoria avrebbe funzionato bene per il caso comune, poiché i programmi tendono a macinare molto sulle stesse stringhe.
Il rovescio della medaglia è che le concatenazioni fanno molti extra String
che sono solo di transizione e diventano solo spazzatura, danneggiando effettivamente le prestazioni della memoria. Hai StringBuffer
e StringBuilder
(in Java, StringBuilder
è anche in .NET) da utilizzare per conservare la memoria in questi casi.
String
s in Java non sono veramente immutabili, puoi cambiare il loro valore usando il reflection eo caricando le classi. Non dovresti dipendere da quella proprietà per sicurezza. Per esempi vedi: Trucco magico in Java
L'immutabilità è buona. Vedi Java efficace. Se dovessi copiare una stringa ogni volta che la passavi, sarebbe un sacco di codice soggetto a errori. Hai anche confusione su quali modifiche influenzano quali riferimenti. Allo stesso modo in cui Integer deve essere immutabile per comportarsi come int, le stringhe devono comportarsi come immutabili per agire come primitivi. In C ++ il passaggio di stringhe per valore fa ciò senza menzione esplicita nel codice sorgente.
C'è un'eccezione per quasi tutte le regole:
using System;
using System.Runtime.InteropServices;
namespace Guess
{
class Program
{
static void Main(string[] args)
{
const string str = "ABC";
Console.WriteLine(str);
Console.WriteLine(str.GetHashCode());
var handle = GCHandle.Alloc(str, GCHandleType.Pinned);
try
{
Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');
Console.WriteLine(str);
Console.WriteLine(str.GetHashCode());
}
finally
{
handle.Free();
}
}
}
}
È in gran parte per motivi di sicurezza. È molto più difficile proteggere un sistema se non ci si può fidare che i propri dispositivi String
siano a prova di manomissione.