it-swarm-eu.dev

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

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

185
chrissie1

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, senza alcuno sforzo da parte vostra.

[...]

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, 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.

202
PRINCESS FLUFF

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 di 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, Sun ha deciso di renderlo immutabile.

102
Jorge Ferreira

In realtà, i motivi della stringa sono immutabili in Java non ha molto a che fare con la sicurezza. I due motivi principali sono i seguenti:

Sicurezza Thead:

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.

Prestazione:

Sebbene sia stato menzionato l'International String, rappresenta solo un piccolo guadagno nell'efficienza della memoria per Java. Vengono internati solo valori letterali di stringhe. Ciò significa che solo le stringhe uguali nel tuo codice sorgente condividerà lo stesso oggetto String. Se il tuo programma crea in modo dinamico stringhe uguali, queste 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 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 finirebbe 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. ha anche alcuni vantaggi di memoria, poiché molte stringhe possono condividere il loro carattere sottostante [].

57
LordOfThePigs

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 su immutabilità .

28
Matt Howells

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à (vedi na lista dei desideri per il prossimo linguaggio di programmazione mainstream ).

11
Esko Luontola

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.

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

7
David Pierre

Wow! Non riesco a credere alla disinformazione qui. Le stringhe essendo immutabili non hanno 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 sta "hackerando" una stringa nella tua app), sarebbero sicuramente molte altre opportunità disponibili per l'hacking.

È un'idea abbastanza nuova che l'immutabilità di String stia risolvendo 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 letteralmente ottimizzato l'archiviazione di una sola istanza di una stringa. Lo svantaggio di questa ottimizzazione è che il codice di runtime che modifica un valore letterale di stringa 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 la stringa letterale "dog" in "cat". Un printf ("cane") comporterebbe la scrittura di "cat" su stdout. Per questo motivo, doveva esserci un modo per proteggersi dal codice che tenta di cambiare i valori letterali delle stringhe (es. Renderli immutabili). Alcuni compilatori (con il supporto del sistema operativo) lo farebbero posizionando letteralmente una stringa in uno speciale segmento di memoria di sola lettura che causerebbe un errore di memoria se si effettuasse un 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 letterali stringa vengono modificati in fase di esecuzione, Java rende semplicemente immutabile la classe String (ad es., non ti dà setter che ti permetterebbero di cambiare il contenuto della stringa). Le stringhe non dovrebbero essere immutabile se non si verificava l'internamento di letterali stringa.

7
Jim Barton

Un fattore è che, se le stringhe fossero mutabili, gli oggetti che archiviano le stringhe dovrebbero stare attenti 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 trattarli come se fossero passati per valore, anche se sono passati per riferimento (il che aiuta anche a risparmiare memoria).

7
Evan DiBiase

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 il precedente si applica solo alle stringhe .NET in quanto Java non ha puntatori. Tuttavia una stringa può essere completamente mutabile usando i puntatori in C #. Non è come i puntatori debbano essere usati, ha uso pratico o è usato in modo sicuro, tuttavia è possibile, piegando così l'intera regola "mutabile". Normalmente non è possibile modificare un indice direttamente di una stringa e questo è l'unico modo. C'è un modo per impedirlo impedendo il puntatore istanze di stringhe o creazione di una copia quando viene puntata una stringa, ma nessuna delle due viene eseguita, il che rende le stringhe in C # non del tutto immutabili.

6
Bauss

Per la maggior parte degli scopi, una "stringa" è (usata/trattata come/considerata/presunta) una significativa nità atomica, proprio come un numero .

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

Dovresti sapere perché. Basta pensarci.

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

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 "manipolare le stringhe", forse stanno davvero parlando di manipolare caratteri per costruire stringhe, e StringBuilder è perfetto per questo. Pensa un po 'a cosa significhi veramente la parola "stringa".

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

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.

3
Triynko

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

3
Andrei Rînea

Stringhe in Java non sono veramente immutabili, puoi cambiare il loro valore usando il riflesso e il caricamento della classe. Non dovresti dipendere da quella proprietà per sicurezza. Per esempi vedi: Trucco magico In Java

2
user80494

È un compromesso. Le stringhe vanno nel pool di stringhe e quando crei 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, poiché i programmi tendono a macinare molto sulle stesse stringhe.

Il rovescio della medaglia è che le concatenazioni creano molte stringhe extra 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.

2
aaronroyer

La decisione di rendere mutevole la stringa in C++ causa molti problemi, vedi questo eccellente articolo di Kelvin Henney su Mad COW Disease .

COW = Copia in scrittura.

2
Motti

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 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 primitivi. In C++ il passaggio di stringhe per valore fa ciò senza menzione esplicita nel codice sorgente.

0

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