Domanda

Perché hanno deciso di rendere la stringa immutabile in Java e .NET (e in alcuni altri linguaggi)?Perché non l'hanno reso mutevole?

È stato utile?

Soluzione

Secondo Java efficace, 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 inclini all'errore e sono più sicuri.

[...]

"Gli oggetti immutabili sono semplici. Un oggetto immutabile può essere esattamente in uno stato, lo stato in cui è stato creato.Se ti assicuri che tutti i costruttori stabiliscano invarianti di classe, è garantito che questi invarianti rimarranno veri per sempre, senza alcuno sforzo da parte tua.

[...]

Gli oggetti immutabili sono intrinsecamente thread-safe;non richiedono sincronizzazione. Non possono essere corrotti da più thread che li accedono contemporaneamente.Questo è di gran lunga l'approccio più semplice per raggiungere la sicurezza dei fili.In effetti, nessun thread può mai osservare alcun effetto di un altro thread su un oggetto immutabile.Perciò, 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 elementi costitutivi per altri oggetti, siano essi mutabili o immutabili.

[...]

L'unico vero svantaggio delle classi immutabili è che richiedono un oggetto separato per ciascun valore distinto.

Altri suggerimenti

Ci sono almeno due ragioni.

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

Il motivo principale per cui la stringa resa immutabile era la sicurezza.Guarda questo esempio:Abbiamo un metodo Apri del 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 la stringa era mutabile, era possibile in qualche modo modificare il proprio contenuto dopo il controllo di autenticazione prima che OS riceva la richiesta dal programma, è possibile richiedere qualsiasi file.Quindi, se hai il diritto di aprire il file di testo nella directory utente ma poi al volo quando in qualche modo riesci a modificare il nome del file, puoi richiedere per 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 "pool di stringhe".Per ottenere l'efficienza della memoria, JVM faranno riferimento all'oggetto stringa dal pool.Non creerà i nuovi oggetti stringa.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 oggetto o creare il nuovo oggetto nel pool.Ci saranno molti riferimenti indicano gli stessi oggetti stringa, se qualcuno cambia il valore, influenzerà tutti i riferimenti.Quindi, Sun 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:

Sicurezza della testa:

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 garantire che sia sicuro condividere stringhe tra thread.Avere una stringa immutabile garantisce che quando si passano stringhe dal thread A a un altro thread B, il thread B non possa modificare inaspettatamente la stringa del thread A.

Questo non solo aiuta a semplificare 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 in qualche modo sincronizzato quando è possibile accedervi da più thread, per assicurarsi che un thread non tenti di leggere il valore del tuo oggetto mentre viene modificato da un altro thread.Una 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 necessitano di sincronizzazione.

Prestazione:

Sebbene sia stato menzionato l'internamento di stringhe, rappresenta solo un piccolo guadagno nell'efficienza della memoria per i programmi Java.Vengono internati solo i valori letterali stringa.Ciò significa che solo le stringhe uguali nel tuo codice sorgente condividerà lo stesso oggetto String.Se il tuo programma crea dinamicamente stringhe uguali, verranno 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 la matrice 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 char[] sottostante di myString ma chi sa che inizia all'indice 0 e termina all'indice 5 di quel char[].Per metterlo in forma grafica, ti ritroveresti con quanto segue:

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

Ciò rende questo tipo di operazioni estremamente economiche, e O(1) poiché l'operazione non dipende né dalla lunghezza della stringa originale, né dalla lunghezza della sottostringa che dobbiamo estrarre.Questo comportamento presenta anche alcuni vantaggi in termini di memoria, poiché molte stringhe possono condividere il carattere sottostante char[].

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.Una tipica applicazione leggerà una stringa 100 volte per ogni volta che è necessario modificare la stringa.Vedi Wikipedia su immutabilità.

Si dovrebbe davvero chiedere: "Perché X dovrebbe essere mutabile?" È meglio l'immutabilità predefinita, a causa dei benefici già menzionati da La principessa Fluff.Dovrebbe essere un'eccezione il fatto che qualcosa sia mutevole.

Sfortunatamente la maggior parte degli attuali linguaggi di programmazione sono impostati per default sulla mutabilità, ma si spera che in futuro l'impostazione predefinita sia più sull'immutabilità (vedi Una lista dei desideri per il prossimo linguaggio di programmazione mainstream).

Un fattore è che, se le stringhe fossero mutabili, gli oggetti che le memorizzano dovrebbero fare attenzione a memorizzarne 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 trattarle come se fossero passate per valore, anche se vengono passate per riferimento (il che aiuta anche a risparmiare memoria).

Oh!Non posso credere alla disinformazione qui.Le stringhe essendo immutabili non hanno nulla con la sicurezza.Se qualcuno ha già accesso agli oggetti in un'applicazione in esecuzione (cosa che si dovrebbe presumere se stai cercando di proteggerti da qualcuno che "hackera" una String nella tua app), ci sarebbero sicuramente molte altre opportunità disponibili per l'hacking.

È un'idea piuttosto nuova che l'immutabilità di String risolva i problemi di threading.Hmm...Ho un oggetto che viene modificato da due thread diversi.Come posso risolvere questo problema?sincronizzare l'accesso all'oggetto?Naawww...non permettiamo a nessuno di modificare l'oggetto: questo risolverà tutti i nostri disordinati problemi di concorrenza!Infatti, rendiamo tutti gli oggetti immutabili, e poi possiamo rimuovere il costrutto sincronizzato dal linguaggio Java.

Il vero motivo (indicato da altri sopra) è l'ottimizzazione della memoria.È abbastanza comune in qualsiasi applicazione che la stessa stringa letterale venga utilizzata ripetutamente.È così comune, infatti, che decenni fa molti compilatori hanno ottimizzato la memorizzazione di una sola istanza di una stringa letterale.Lo svantaggio di questa ottimizzazione è che il codice runtime che modifica una stringa letterale introduce un problema perché modifica l'istanza per tutto l'altro codice che la condivide.Ad esempio, non sarebbe opportuno che una funzione da qualche parte in un'applicazione cambiasse la stringa letterale "cane" in "gatto".Un printf("dog") comporterebbe la scrittura di "cat" su stdout.Per questo motivo, era necessario che ci fosse un modo per proteggersi dal codice che tenta di modificare le stringhe letterali (ad es.e., renderli immutabili).Alcuni compilatori (con il supporto del sistema operativo) potrebbero ottenere ciò inserendo la stringa letterale in uno speciale segmento di memoria di sola lettura che causerebbe un errore di memoria se fosse effettuato un tentativo di scrittura.

In Java questo è noto come internamento.Il compilatore Java qui sta semplicemente seguendo un'ottimizzazione della memoria standard eseguita dai compilatori per decenni.E per risolvere lo stesso problema relativo alla modifica di questi valori letterali stringa in fase di esecuzione, Java rende semplicemente immutabile la classe String (i.e, non ti fornisce setter che ti consentano di modificare il contenuto della stringa).Le stringhe non dovrebbero essere immutabili se non si verificasse l'internamento delle stringhe letterali.

La stringa non è un tipo primitivo, ma normalmente vuoi usarlo con la semantica del valore, cioècome un valore.

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

La stringa come oggetto ha naturalmente una semantica del puntatore, per ottenere anche la semantica del valore deve essere immutabile.

So che è una scocciatura, 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 anche 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 fa funzionare quanto segue

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

Conclusione:Sono in uno stato immutabile noto al compilatore.Ovviamente quanto sopra si applica solo alle stringhe .NET poiché Java non ha puntatori.Tuttavia una stringa può essere completamente modificabile utilizzando i puntatori in C#.Non è il modo in cui devono essere utilizzati i puntatori, ha un utilizzo pratico o viene utilizzato in modo sicuro;è tuttavia possibile, piegando così l'intera regola del "mutevole".Normalmente non è possibile modificare direttamente l'indice di una stringa e questo è l'unico modo.Esiste un modo per evitare che ciò accada non consentendo istanze di puntatori di stringhe o creando una copia quando si punta a una stringa, ma nessuna delle due operazioni 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 come) un significato unità atomica, proprio come un numero.

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

Dovresti sapere perché.Pensaci e basta.

Odio dirlo, ma sfortunatamente ne stiamo discutendo perché la nostra lingua fa schifo, e stiamo cercando di usare una sola parola, corda, per descrivere un concetto o una classe di oggetti complessi e contestualmente situati.

Eseguiamo calcoli e confronti con "stringhe" in modo simile a come facciamo con i numeri.Se le stringhe (o gli interi) fossero mutabili, dovremmo scrivere un codice speciale per bloccare i loro valori in forme locali immutabili per 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 lo considerano semplicemente come un insieme di personaggi, senza uno scopo particolare in mente, ovviamente rimarranno inorriditi dal fatto che qualcuno ho appena deciso che non dovrebbero essere in grado di manipolare quei personaggi.Ma la classe "string" non è solo un array di caratteri.È un STRING, non un char[].Esistono alcuni presupposti di base sul concetto a cui ci riferiamo come "stringa" e generalmente può essere descritto come un'unità atomica significativa di dati codificati come un numero.Quando le persone parlano di "manipolare le stringhe", forse parlano davvero di manipolazione caratteri costruire stringhe, e uno StringBuilder è ottimo per questo.Basta pensare un po' a cosa significa veramente la parola "stringa".

Consideriamo per un momento come sarebbe se le stringhe fossero mutabili.La seguente funzione API potrebbe essere indotta a restituire informazioni per un utente diverso se il file mutevole la stringa del nome utente viene modificata intenzionalmente o involontariamente da un altro thread mentre questa funzione la 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 degli accessi", ma anche la "sicurezza" e la "garanzia della correttezza".Se non è possibile scrivere facilmente un metodo e non è possibile fare affidamento su di esso per eseguire un semplice calcolo o confronto in modo affidabile, non è sicuro chiamarlo così, ma sarebbe sicuro mettere in discussione il linguaggio di programmazione stesso.

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

È un compromesso.Le stringhe entrano nel pool di stringhe e quando crei più stringhe identiche condividono la stessa memoria.I progettisti hanno pensato che questa tecnica di risparmio della memoria avrebbe funzionato bene per il caso comune, poiché i programmi tendono a lavorare molto sulle stesse stringhe.

Lo svantaggio è che le concatenazioni creano molte stringhe extra che sono solo transitorie e diventano semplicemente spazzatura, danneggiando effettivamente le prestazioni della memoria.Hai StringBuffer e StringBuilder (in Java, StringBuilder è anche in .NET) da utilizzare per preservare la memoria in questi casi.

La decisione di avere stringhe mutabili in C++ causa molti problemi, vedere questo eccellente articolo di Kelvin Henney a riguardo Morbo della mucca pazza.

COW = Copia su scrittura.

Le stringhe in Java non sono veramente immutabili, puoi modificare il loro valore utilizzando la riflessione e/o il caricamento della classe.Non dovresti dipendere da quella proprietà per la sicurezza.Per esempi vedere: Trucco magico in Java

L'immutabilità è buona.Vedi Java efficace.Se dovessi copiare una String ogni volta che la passi in giro, si tratterebbe di molto codice soggetto a errori.Hai anche confusione su quali modifiche influiscono su quali riferimenti.Allo stesso modo in cui Integer deve essere immutabile per comportarsi come int, le stringhe devono comportarsi come immutabili per agire come primitive.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 ragioni di sicurezza.È molto più difficile proteggere un sistema se non puoi fidarti che le tue stringhe siano a prova di manomissione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top