Perché le stringhe non possono essere modificate in Java e .NET?

Perché hanno deciso di rendere la stringa immutabile in Java e .NET (e in alcune altre lingue)? Perché non lo hanno reso mutabile?

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 object immutabile può trovarsi esattamente in uno stato, lo stato in cui è stato creato. Se si assicura che tutti i costruttori stabiliscano invarianti di class, allora è garantito che questi invarianti rimarranno veri per tutto il tempo, nessuno sforzo da parte tua.

[…]

Gli oggetti immutabili sono intrinsecamente sicuri per i thread; non richiedono alcuna sincronizzazione. Non possono essere danneggiati da più thread che li accedono contemporaneamente. Questo è di gran lunga l’approccio più semplice per ottenere la sicurezza del filo. In effetti, nessun thread può mai osservare alcun effetto di un altro thread su un object 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 costituiscono ottimi mattoni per altri oggetti, mutabili o immutabili.

[…]

L’unico vero svantaggio delle classi immutabili è che richiedono un object separato per ogni valore distinto.

Ci sono almeno due ragioni.

Primo: sicurezza http://www.javafaq.nu/java-article1060.html

La ragione principale per cui String reso immutabile era la sicurezza. Guarda questo esempio: abbiamo un metodo di apertura file con controllo dell’accesso. Passiamo una stringa a questo metodo per elaborare l’autenticazione che è necessaria prima che la chiamata venga passata al sistema operativo. Se String era mutabile era ansible in qualche modo modificare il suo contenuto dopo il controllo dell’autenticazione prima che il sistema operativo ottenga la richiesta dal programma, quindi è ansible richiedere qualsiasi file. Quindi se hai il diritto di aprire un file di testo nella directory degli utenti, ma 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à ansible 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 ottenere l’efficienza della memoria, JVM farà riferimento all’object String dal pool. Non creerà i nuovi oggetti String. Quindi, ogni volta che crei una nuova stringa letterale, JVM controllerà nel pool se esiste già o meno. Se già presente nel pool, basta dare il riferimento allo stesso object o creare il nuovo object nel pool. Ci saranno molti riferimenti che puntano agli stessi oggetti String, se qualcuno cambia il valore, influenzerà tutti i riferimenti. Quindi, il sole ha deciso di renderlo immutabile.

In realtà, le ragioni per cui stringhe sono immutabili in java non hanno molto a che fare con la sicurezza. I due motivi principali sono i seguenti:

Thead Safety:

Le stringhe sono tipi di oggetti estremamente usati. È quindi più o meno garantito per essere utilizzato in un ambiente multi-thread. Le stringhe sono immutabili per assicurarsi che sia sicuro condividere stringhe tra i thread. Avere una stringa immutabile garantisce che quando si passano stringhe dal thread A a un altro thread B, il thread B non può modificare in modo imprevisto la stringa del thread A.

Questo non solo semplifica il compito già piuttosto complicato della programmazione multi-thread, ma aiuta anche con le prestazioni delle applicazioni multi-thread. L’accesso agli oggetti mutabili deve essere sincronizzato in qualche modo quando è ansible accedervi da più thread, per assicurarsi che un thread non tenti di leggere il valore dell’object mentre viene modificato da un altro thread. La corretta sincronizzazione è difficile da eseguire correttamente per il programmatore e costosa in fase di esecuzione. Gli oggetti immutabili non possono essere modificati e quindi non hanno bisogno di sincronizzazione.

Prestazione:

Mentre l’interning delle stringhe è stato menzionato, 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 object String. Se il tuo programma crea dynamicmente string che sono uguali, saranno rappresentati in oggetti diversi.

Ancora più importante, stringhe immutabili consentono loro di condividere i propri dati interni. Per molte operazioni con le stringhe, ciò significa che non è necessario copiare la matrice sottostante di caratteri. Ad esempio, supponi di voler prendere i primi cinque caratteri di String. In Java, chiameresti myString.substring (0,5). In questo caso, ciò che il metodo substring () fa è semplicemente creare un nuovo object String che condivide il char sottostante di myString [] ma che sa che inizia dall’indice 0 e termina con l’indice 5 di quel char []. Per mettere questo in forma grafica, si finirebbe con il seguente:

  | myString | vv "The quick brown fox jumps over the lazy dog" < -- shared char[] ^ ^ | | myString.substring(0,5) 

Ciò rende estremamente economico questo tipo di operazioni, e O (1) poiché l'operazione non dipende né dalla lunghezza della stringa originale, né dalla lunghezza della sottostringa da estrarre. Questo comportamento ha anche alcuni vantaggi di memoria, dal momento che molte stringhe possono condividere il loro char sottostante [].

Filo sicurezza e prestazioni. Se una stringa non può essere modificata, è sicuro e veloce passare un riferimento tra più thread. Se le stringhe fossero modificabili, dovresti sempre copiare tutti i byte della stringa in una nuova istanza o fornire la sincronizzazione. Una tipica applicazione leggerà una stringa 100 volte per ogni volta che la stringa deve essere modificata. Vedi wikipedia su immutabilità .

Uno dovrebbe davvero chiedere, “perché X dovrebbe essere mutabile?” È meglio impostare l’immutabilità, a causa dei benefici già menzionati da Princess Fluff . Dovrebbe essere un’eccezione che qualcosa è 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 sia più sull’immutablità (vedere A Wish List per il prossimo linguaggio di programmazione mainstream ).

Un fattore è che, se le stringhe fossero modificabili, gli oggetti che memorizzano le stringhe dovrebbero fare attenzione a conservare le copie, per evitare che i loro dati interni cambino senza preavviso. Dato che le stringhe sono un tipo abbastanza primitivo come i numeri, è bello quando si possono trattare come se fossero passati per valore, anche se sono passati per riferimento (che aiuta anche a risparmiare sulla memoria).

La stringa non è un tipo primitivo, ma normalmente lo si vuole usare con la semantica del valore, cioè come un valore.

Un valore è qualcosa di cui ti puoi fidare che non cambierà alle tue spalle. Se scrivi: String str = someExpr(); Non vuoi che cambi, a meno che TU non faccia qualcosa con str.

String come Object ha una semantica puntatore naturale, per ottenere semantica del valore e deve essere immutabile.

Wow! Non posso credere alla disinformazione qui. Le stringhe essendo immutabili non hanno nulla con sicurezza. Se qualcuno ha già accesso agli oggetti in un’applicazione in esecuzione (che dovrebbe essere assunto se si sta cercando di evitare qualcuno che “hacking” una stringa nella propria app), sarebbero sicuramente molte altre opportunità disponibili per l’hacking.

È un’idea del tutto romanzesca che l’immutabilità di String stia affrontando problemi di threading. Hmmm … Ho un object che viene modificato da due thread differenti. Come posso risolvere questo? sincronizzare l’accesso all’object? Naawww … non permettiamo a nessuno di cambiare l’object – risolverà tutti i nostri problemi di concorrenza disordinati! In effetti, rendiamo tutti gli oggetti immutabili, e quindi possiamo rimuovere il contrallo sincronizzato dal linguaggio Java.

La vera ragione (sottolineata da altri sopra) è l’ottimizzazione della memoria. È abbastanza comune in qualsiasi applicazione che la stessa stringa letterale sia usata ripetutamente. È così comune, infatti, che decenni fa, molti compilatori hanno fatto l’ottimizzazione di memorizzare solo una singola istanza di una stringa letterale. Lo svantaggio di questa ottimizzazione è che il codice runtime che modifica un letterale stringa introduce un problema perché modifica l’istanza per tutti gli altri codici che la condividono. Ad esempio, non sarebbe opportuno per una funzione da qualche parte in un’applicazione modificare la stringa letterale “cane” in “gatto”. Un printf (“cane”) comporterebbe la scrittura di “cat” su stdout. Per questo motivo, c’era bisogno di essere un modo per proteggersi dal codice che tenta di cambiare i letterali stringa (cioè renderli immutabili). Alcuni compilatori (con supporto dal sistema operativo) otterrebbero questo risultato inserendo il letterale stringa in uno speciale segmento di memoria di sola lettura che causerebbe un errore di memoria se fosse stato effettuato un tentativo di scrittura.

In Java questo è noto come internamento. Il compilatore Java qui segue solo un’ottimizzazione della memoria standard eseguita dai compilatori per decenni. E per risolvere lo stesso problema di queste stringhe letterali che vengono modificate in fase di runtime, Java rende semplicemente immutabile la class String (cioè, non fornisce setter che consentano di modificare il contenuto di String). Le stringhe non dovrebbero essere immutabili se non si verificano internamenti di stringhe letterali.

So che questo è un urto, 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; } } } 

Che rende il seguente lavoro

 s.MutableReplaceIndex('1', 0); s.MutableReplaceIndex('2', 1); s.MutableReplaceIndex('3', 2); 

Conclusione: sono in uno stato immutabile che è noto al compilatore. Di quanto sopra si applica solo alle stringhe .NET poiché Java non ha puntatori. Tuttavia una stringa può essere interamente mutabile usando i puntatori in C #. Non è il modo in cui i puntatori sono destinati ad essere utilizzati, ha un uso pratico o è utilizzato in modo sicuro; è comunque ansible, piegando così l’intera regola “mutabile”. Normalmente non è ansible modificare un indice direttamente di una stringa e questo è l’unico modo. C’è un modo in cui questo può essere prevenuto non consentendo le istanze del puntatore di stringhe o facendo una copia quando viene puntata una stringa, ma non viene fatto nessuno, il che rende le stringhe in C # non completamente immutabili.

Per la maggior parte degli scopi, una “stringa” è (usata / trattata come / pensato / assunta per essere) un’unità atomica significativa , proprio come un numero .

Chiedere perché i singoli caratteri di una stringa non siano mutabili è quindi come chiedere perché i singoli bit di un intero non siano mutabili.

Dovresti sapere perché. Pensaci.

Odio dirlo, ma sfortunatamente stiamo discutendo di questo perché il nostro linguaggio fa schifo e stiamo cercando di usare una singola parola, una stringa , per descrivere un concetto o una class di oggetti complessi, collocati in un contesto.

Eseguiamo calcoli e confronti con “stringhe” simili a come facciamo con i numeri. Se le stringhe (o interi) fossero mutabili, dovremmo scrivere un codice speciale per bloccare i loro valori in forms locali immutabili per poter eseguire qualsiasi tipo di calcolo in modo affidabile. Pertanto, è meglio pensare a una stringa come un identificatore numerico, ma invece di essere lunghi 16, 32 o 64 bit, potrebbe essere lunga centinaia di bit.

Quando qualcuno dice “stringa”, pensiamo tutti a cose diverse. Coloro che la pensano semplicemente come un insieme di personaggi, senza un particolare scopo in mente, saranno ovviamente sconvolti dal fatto che qualcuno abbia appena deciso che non dovrebbero essere in grado di manipolare quei personaggi. Ma la class “stringa” non è solo una serie di personaggi. È una STRING , non una char[] . Ci sono alcune assunzioni di base sul concetto che chiamiamo “stringa”, e generalmente può essere descritto come unità atomica significativa di dati codificati come un numero. Quando le persone parlano di “manipolazione delle stringhe”, forse stanno davvero parlando di manipolare i caratteri per creare stringhe e un StringBuilder è perfetto per questo. Pensa un po ‘a cosa significa realmente “stringa”.

Considera per un momento come sarebbe se le stringhe fossero modificabili. La seguente funzione API potrebbe essere indotta a restituire informazioni per un utente diverso se la stringa del nome utente mutabile viene intenzionalmente o non intenzionalmente modificata da un altro thread mentre questa funzione lo sta utilizzando:

 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 dell’accesso”, ma anche “sicurezza” e “garanzia di correttezza”. Se un metodo non può essere facilmente scritto e dipende da un semplice calcolo o confronto, non è sicuro chiamarlo, ma sarebbe sicuro mettere in discussione il linguaggio di programmazione stesso.

È un compromesso. Le stringhe entrano nel pool di stringhe e quando si creano più stringhe identiche condividono la stessa memoria. I progettisti hanno pensato che questa tecnica di salvataggio della memoria avrebbe funzionato bene per il caso comune, dal momento che i programmi tendono a macinare molto le stesse stringhe.

Il rovescio della medaglia è che le concatenazioni fanno un sacco di stringhe extra che sono solo transitorie e diventano spazzatura, danneggiando le prestazioni della memoria. Hai StringBuffer e StringBuilder (in Java, StringBuilder è anche in. NET) da utilizzare per conservare la memoria in questi casi.

La decisione di avere una stringa mutabile in C ++ causa un sacco di problemi, vedi questo eccellente articolo di Kelvin Henney su Mad COW Disease .

COW = Copia su scrittura.

Le stringhe in Java non sono veramente immutabili, puoi cambiarne il valore usando il reflection e il caricamento della class. Non dovresti dipendere da quella proprietà per sicurezza. Per esempi vedi: Magic Trick In Java

L’immutabilità non è così strettamente legata alla sicurezza. Per questo, almeno in .NET, ottieni la class SecureString.

L’immutabilità è buona. Vedi efficace Java. Se dovessi copiare una stringa ogni volta che la passavi in ​​giro, sarebbe un sacco di codice sobject a errori. Hai anche confusione su quali modifiche influenzano i riferimenti. Allo stesso modo che Integer deve essere immutabile per comportarsi come int, le stringhe devono comportarsi come immutabili per comportarsi come i primitivi. In C ++ passare le stringhe in base al valore lo fa senza menzione esplicita nel codice sorgente.

C’è un’eccezione per quasi quasi ogni regola:

 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(); } } } } 

La stringa è un object immutabile (una volta creato non può essere modificato). L’object creato come stringa viene memorizzato nel lotto di stringhe costanti. Ogni object immutabile in Java è thread-safe, il che implica che String è anche thread-safe. La stringa non può essere utilizzata da due thread contemporaneamente. La stringa assegnata una volta non può essere modificata.

 String demo = " hello " ; 

// L’object sopra riportato è memorizzato in un pool di stringhe costante e il suo valore non può essere modificato.

 demo="Bye" ; 

// la nuova stringa “Bye” viene creata in un pool costante e referenziata dalla variabile demo

// La stringa “hello” esiste ancora nel pool di costanti di stringhe e il suo valore non è sovrascritto, ma abbiamo perso il riferimento alla stringa “ciao”

È in gran parte per motivi di sicurezza. È molto più difficile proteggere un sistema se non ci si può fidare che le stringhe siano a prova di manomissione.