it-swarm.com.de

Warum können Strings in Java und .NET nicht geändert werden?

Warum haben sie beschlossen, Zeichenfolgen in Java und .NET (und einigen anderen Sprachen) unveränderlich zu machen? Warum haben sie sie nicht veränderbar gemacht?

185
chrissie1

Gemäß Effective Java , Kapitel 4, Seite 73, 2. Ausgabe:

"Dafür gibt es viele gute Gründe: Unveränderliche Klassen sind einfacher zu entwerfen, zu implementieren und zu verwenden als veränderbare Klassen. Sie sind weniger fehleranfällig und sicherer.

[...]

" Unveränderliche Objekte sind einfach. Ein unveränderliches Objekt kann sich in genau einem Zustand befinden, dem Zustand, in dem es erstellt wurde. Wenn Sie sicherstellen, dass alle Konstruktoren eingerichtet sind Klasse Invarianten, dann ist es garantiert, dass diese Invarianten für alle Zeiten wahr bleiben, ohne dass Sie sich bemühen müssen.

[...]

Unveränderliche Objekte sind inhärent threadsicher. Sie erfordern keine Synchronisierung. Sie können nicht durch mehrere Threads beschädigt werden, die gleichzeitig auf sie zugreifen. Dies ist bei weitem der einfachste Weg, um die Fadensicherheit zu erreichen. Tatsächlich kann kein Thread jemals einen Effekt eines anderen Threads auf ein unveränderliches Objekt beobachten. Daher können unveränderliche Objekte frei geteilt werden

[...]

Weitere kleine Punkte aus demselben Kapitel:

Sie können nicht nur unveränderliche Objekte freigeben, sondern auch deren Interna.

[...]

Unveränderliche Objekte sind große Bausteine ​​für andere Objekte, egal ob veränderlich oder unveränderlich.

[...]

Der einzige wirkliche Nachteil unveränderlicher Klassen besteht darin, dass für jeden einzelnen Wert ein separates Objekt erforderlich ist.

202
PRINCESS FLUFF

Dafür gibt es mindestens zwei Gründe.

First - security http://www.javafaq.nu/Java-article1060.html

Der Hauptgrund, warum String unveränderlich gemacht wurde, war die Sicherheit. Schauen Sie sich dieses Beispiel an: Wir haben eine Methode zum Öffnen von Dateien mit Login-Prüfung. Wir übergeben dieser Methode einen String, um die Authentifizierung zu verarbeiten, die erforderlich ist, bevor der Aufruf an das Betriebssystem weitergeleitet wird. Wenn String änderbar war, war es möglich, seinen Inhalt nach der Authentifizierungsprüfung zu ändern, bevor das Betriebssystem eine Anforderung vom Programm erhält. Dann ist es möglich, eine beliebige Datei anzufordern. Wenn Sie also das Recht haben, eine Textdatei im Benutzerverzeichnis zu öffnen, aber dann sofort, wenn Sie es irgendwie schaffen, den Dateinamen zu ändern, können Sie das Öffnen einer "passwd" -Datei oder einer anderen anfordern. Dann kann eine Datei geändert werden und es ist möglich, sich direkt beim Betriebssystem anzumelden.

Zweitens - Speichereffizienz http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable. html

JVM verwaltet intern den "String Pool". Um die Speichereffizienz zu erhöhen, verweist JVM auf das String-Objekt aus dem Pool. Die neuen String-Objekte werden nicht erstellt. Wenn Sie also ein neues Zeichenfolgenliteral erstellen, prüft JVM im Pool, ob es bereits vorhanden ist oder nicht. Wenn bereits im Pool vorhanden, geben Sie einfach den Verweis auf dasselbe Objekt an oder erstellen Sie das neue Objekt im Pool. Es gibt viele Referenzen, die auf dieselben String-Objekte verweisen. Wenn jemand den Wert ändert, wirkt sich dies auf alle Referenzen aus. Also beschloss Sun, es unveränderlich zu machen.

102
Jorge Ferreira

Tatsächlich sind die Gründe für die Unveränderlichkeit der Zeichenfolge in Java hat nicht viel mit Sicherheit zu tun. Die beiden Hauptgründe sind die folgenden:

Thead Sicherheit:

Strings sind extrem weit verbreitete Objekttypen. Es ist daher mehr oder weniger garantiert, dass es in einer Umgebung mit mehreren Threads verwendet wird. Zeichenfolgen sind unveränderlich, um sicherzustellen, dass es sicher ist, Zeichenfolgen zwischen Threads zu teilen. Durch unveränderliche Zeichenfolgen wird sichergestellt, dass bei der Übergabe von Zeichenfolgen von Thread A an einen anderen Thread B die Zeichenfolge von Thread A nicht unerwartet geändert werden kann.

Dies vereinfacht nicht nur die ohnehin schon recht komplizierte Aufgabe der Multithread-Programmierung, sondern trägt auch zur Leistung von Multithread-Anwendungen bei. Der Zugriff auf veränderbare Objekte muss irgendwie synchronisiert werden, wenn von mehreren Threads aus auf sie zugegriffen werden kann, um sicherzustellen, dass ein Thread nicht versucht, den Wert Ihres Objekts zu lesen, während es von einem anderen Thread geändert wird. Eine ordnungsgemäße Synchronisation ist für den Programmierer schwierig und zur Laufzeit teuer. Unveränderliche Objekte können nicht geändert werden und müssen daher nicht synchronisiert werden.

Performance:

Obwohl das Internieren von Strings erwähnt wurde, bedeutet es nur einen geringen Gewinn an Speichereffizienz für Java Programme. Es werden nur String-Literale interniert. Dies bedeutet, dass nur die Strings in Ihrem identisch sind. -source code teilt dasselbe String-Objekt. Wenn Ihr Programm dynamisch identische Zeichenfolgen erstellt, werden diese in verschiedenen Objekten dargestellt.

Noch wichtiger ist, dass unveränderliche Zeichenfolgen es ihnen ermöglichen, ihre internen Daten gemeinsam zu nutzen. Für viele Zeichenfolgenoperationen bedeutet dies, dass das zugrunde liegende Zeichenarray nicht kopiert werden muss. Angenommen, Sie möchten die fünf ersten Zeichen von String verwenden. In Java würden Sie myString.substring (0,5) aufrufen. In diesem Fall erstellt die substring () -Methode einfach ein neues String-Objekt, das das zugrunde liegende Zeichen [] von myString verwendet, aber wer weiß, dass es am Index 0 beginnt und am Index 5 dieses Zeichens [] endet. Um dies grafisch darzustellen, würden Sie am Ende Folgendes tun:

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

Dies macht diese Art von Operationen äußerst kostengünstig und O(1) da die Operation weder von der Länge der ursprünglichen Zeichenfolge noch von der Länge der zu extrahierenden Teilzeichenfolge abhängt hat auch einige Memory-Vorteile, da viele Strings ihre zugrunde liegenden char [] teilen können.

57
LordOfThePigs

Fadensicherheit und -leistung. Wenn eine Zeichenfolge nicht geändert werden kann, kann eine Referenz sicher und schnell zwischen mehreren Threads ausgetauscht werden. Wenn Strings veränderbar wären, müssten Sie immer alle Bytes des Strings in eine neue Instanz kopieren oder eine Synchronisation bereitstellen. Eine typische Anwendung liest eine Zeichenfolge 100 Mal, wenn diese Zeichenfolge geändert werden muss. Siehe Wikipedia auf nveränderlichkeit .

28
Matt Howells

Man sollte sich wirklich fragen: "Warum sollte X veränderlich sein?" Aufgrund der Vorteile, die bereits von Princess Fluff erwähnt wurden, ist es besser, standardmäßig Unveränderlichkeit zu wählen. Es sollte eine Ausnahme sein, dass etwas veränderlich ist.

Leider sind die meisten aktuellen Programmiersprachen standardmäßig veränderbar, aber hoffentlich ist die Standardeinstellung in Zukunft eher auf Unveränderlichkeit ausgerichtet (siehe Wunschliste für die nächste Mainstream-Programmiersprache ).

11
Esko Luontola

String ist kein primitiver Typ, aber normalerweise möchten Sie ihn mit Wertsemantik verwenden, d. H. Wie ein Wert.

Ein Wert ist etwas, dem Sie vertrauen können, das sich nicht hinter Ihrem Rücken ändert. Wenn Sie schreiben: String str = someExpr(); Sie möchten nicht, dass es sich ändert, es sei denn, SIE tun etwas mit str.

String als Objekt hat natürlich Zeigersemantik, um auch Wertesemantik zu erhalten, muss es unveränderlich sein.

7
David Pierre

Wow! Ich kann die Fehlinformationen hier nicht glauben. Saiten, die unveränderlich sind, haben nichts mit Sicherheit zu tun. Wenn jemand bereits Zugriff auf die Objekte in einer laufenden Anwendung hat (was anzunehmen ist, wenn Sie versuchen, sich davor zu schützen, dass jemand eine Zeichenfolge in Ihrer App "hackt"), stehen ihm sicherlich noch viele andere Möglichkeiten zum Hacken zur Verfügung.

Es ist eine ziemlich neue Idee, dass die Unveränderlichkeit von String Threading-Probleme behebt. Hmmm ... Ich habe ein Objekt, das von zwei verschiedenen Threads geändert wird. Wie löse ich das? Zugriff auf das Objekt synchronisieren? Naawww ... lassen wir niemanden das Objekt ändern - das wird all unsere unordentlichen Nebenläufigkeitsprobleme beheben! Lassen Sie uns tatsächlich alle Objekte unveränderlich machen, und dann können wir die synchronisierte Struktur aus der Sprache Java) entfernen.

Der eigentliche Grund (von anderen oben herausgestellt) ist die Speicheroptimierung. In jeder Anwendung ist es durchaus üblich, dass dasselbe Zeichenfolgenliteral wiederholt verwendet wird. Tatsächlich ist es so verbreitet, dass viele Compiler vor Jahrzehnten die Optimierung vorgenommen haben, nur eine einzige Instanz eines String-Literal zu speichern. Der Nachteil dieser Optimierung ist, dass Laufzeitcode, der ein Zeichenfolgenliteral ändert, ein Problem verursacht, da er die Instanz für den gesamten anderen Code ändert, der sie gemeinsam verwendet. Zum Beispiel wäre es nicht gut, wenn eine Funktion irgendwo in einer Anwendung das String-Literal "dog" in "cat" ändern würde. Ein printf ("dog") würde dazu führen, dass "cat" nach stdout geschrieben wird. Aus diesem Grund musste es eine Möglichkeit geben, sich vor Code zu schützen, der versucht, Zeichenfolgenliterale zu ändern (d. H. Sie unveränderlich zu machen). Einige Compiler (mit Unterstützung des Betriebssystems) würden dies erreichen, indem sie ein Zeichenfolgenliteral in ein spezielles schreibgeschütztes Speichersegment einfügen, das bei einem Schreibversuch einen Speicherfehler verursachen würde.

In Java wird dies als Internierung bezeichnet. Der Java) - Compiler folgt hier nur einer Standardspeicheroptimierung, die Compiler seit Jahrzehnten durchgeführt haben Wenn diese String-Literale zur Laufzeit geändert werden, macht Java macht die String-Klasse einfach unveränderlich (d. h. Sie erhalten keine Setter, mit denen Sie den String-Inhalt ändern könnten) unveränderlich, wenn keine Internierung von String-Literalen stattgefunden hat.

7
Jim Barton

Ein Faktor ist, dass Objekte, die Zeichenfolgen speichern, vorsichtig sein müssten, um Kopien zu speichern, wenn Zeichenfolgen veränderbar wären, damit sich ihre internen Daten nicht ohne vorherige Ankündigung ändern. Angesichts der Tatsache, dass Strings ein ziemlich primitiver Typ sind, wie Zahlen, ist es schön, wenn man sie so behandeln kann, als ob sie als Wert übergeben wurden, auch wenn sie als Referenz übergeben werden (was auch dazu beiträgt, Speicherplatz zu sparen).

7
Evan DiBiase

Ich weiß, das ist eine Beule, aber ... Sind sie wirklich unveränderlich? Folgendes berücksichtigen.

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

Sie könnten es sogar zu einer Erweiterungsmethode machen.

public static class Extensions
{
    public static unsafe void MutableReplaceIndex(this string s, char c, int i)
    {
        fixed (char* ptr = s)
        {
            *((char*)(ptr + i)) = c;
        }
    }
}

Was die folgende Arbeit macht

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

Fazit: Sie befinden sich in einem unveränderlichen Zustand, der dem Compiler bekannt ist. Natürlich gilt das Obige nur für .NET-Strings, da Java keine Zeiger hat. Ein String kann jedoch mithilfe von Zeigern in C # vollständig veränderbar sein. Es ist nicht so, wie Zeiger verwendet werden sollen, hat Praktische Verwendung oder sichere Verwendung, es ist jedoch möglich, wodurch die gesamte "veränderbare" Regel gebogen wird Normalerweise kann ein Index nicht direkt von einem String geändert werden, und dies ist der einzige Weg Instanzen von Zeichenfolgen oder Erstellen einer Kopie, wenn auf eine Zeichenfolge verwiesen wird, aber keine von beiden ausgeführt wird, wodurch Zeichenfolgen in C # nicht vollständig unveränderlich werden.

6
Bauss

Für die meisten Zwecke ist eine "Zeichenfolge" (verwendet/behandelt als/gedacht/angenommen als) eine sinnvolle atomare Einheit genau wie eine Zahl .

Zu fragen, warum die einzelnen Zeichen einer Zeichenfolge nicht veränderbar sind, ist daher wie zu fragen, warum die einzelnen Bits einer Ganzzahl nicht veränderbar sind.

Du solltest wissen warum. Denken Sie einfach darüber nach.

Ich hasse es, es zu sagen, aber leider debattieren wir darüber, weil unsere Sprache scheiße ist und wir versuchen, ein einziges Wort zu verwenden, string , um ein komplexes, kontextuell angeordnetes Konzept oder eine Objektklasse zu beschreiben.

Wir führen Berechnungen und Vergleiche mit "Strings" durch, ähnlich wie wir es mit Zahlen machen. Wenn Zeichenfolgen (oder ganze Zahlen) veränderbar wären, müssten wir speziellen Code schreiben, um ihre Werte in unveränderlichen lokalen Formen zu fixieren, damit jede Art von Berechnung zuverlässig durchgeführt werden kann. Aus diesem Grund ist es am besten, sich eine Zeichenfolge wie eine numerische Kennung vorzustellen, die jedoch nicht 16, 32 oder 64 Bit, sondern Hunderte von Bit lang sein kann.

Wenn jemand "string" sagt, denken wir alle über verschiedene Dinge nach. Diejenigen, die es einfach als eine Reihe von Zeichen ohne besonderen Zweck betrachten, werden natürlich entsetzt sein, dass jemand gerade entschieden hat , dass sie dies nicht tun sollten in der Lage sein, diese Zeichen zu manipulieren. Die Klasse "string" ist jedoch nicht nur ein Array von Zeichen. Es ist ein STRING, kein char[]. Es gibt einige grundlegende Annahmen über das Konzept, das wir als "Zeichenfolge" bezeichnen, und es kann allgemein als sinnvolle atomare Einheit codierter Daten wie eine Zahl beschrieben werden. Wenn von "Manipulieren von Strings" die Rede ist, spricht man vielleicht wirklich davon, Zeichen zu erstellen Strings, und ein StringBuilder eignet sich hervorragend dafür. Denken Sie nur ein wenig darüber nach, was der Wort-String wirklich bedeutet.

Überlegen Sie sich für einen Moment, wie es wäre, wenn Saiten veränderlich wären. Die folgende API-Funktion könnte dazu verleitet werden, Informationen für einen anderen Benutzer zurückzugeben, wenn die veränderbare Benutzername-Zeichenfolge absichtlich oder unbeabsichtigt von einem anderen Thread geändert wird, während diese Funktion verwendet wird es:

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

Bei Sicherheit geht es nicht nur um "Zugangskontrolle", sondern auch um "Sicherheit" und "Gewährleistung der Korrektheit". Wenn eine Methode nicht einfach geschrieben werden kann und eine einfache Berechnung oder ein einfacher Vergleich nicht zuverlässig durchgeführt werden können, ist es nicht sicher, sie aufzurufen, aber es wäre sicher, die Programmiersprache selbst in Frage zu stellen.

3
Triynko

Unveränderlichkeit ist nicht so eng mit Sicherheit verbunden. Dafür erhalten Sie zumindest in .NET die SecureString-Klasse.

3
Andrei Rînea

Zeichenfolgen in Java sind nicht wirklich unveränderlich. Sie können ihre Werte durch Reflektion und/oder Laden von Klassen ändern. Aus Sicherheitsgründen sollten Sie nicht von dieser Eigenschaft abhängig sein. Beispiele: Zaubertrick In Java

2
user80494

Es ist ein Kompromiss. Zeichenfolgen werden in den Zeichenfolgenpool aufgenommen. Wenn Sie mehrere identische Zeichenfolgen erstellen, teilen sich diese den gleichen Speicher. Die Designer gingen davon aus, dass diese Technik zum Speichern von Speicher für den üblichen Fall gut geeignet ist, da Programme dazu neigen, häufig über dieselben Zeichenfolgen zu schleifen.

Der Nachteil ist, dass durch Verkettungen viele zusätzliche Zeichenfolgen entstehen, die nur vorübergehend sind und nur zu Müll werden, wodurch die Speicherleistung tatsächlich beeinträchtigt wird. Sie haben StringBuffer und StringBuilder (in Java ist StringBuilder auch in .NET enthalten), um in diesen Fällen Speicherplatz zu sparen.

2
aaronroyer

Die Entscheidung, die Zeichenfolge in C++ änderbar zu machen, verursacht eine Menge Probleme. Lesen Sie diesen ausgezeichneten Artikel von Kelvin Henney über Mad COW Disease .

COW = Copy On Write.

2
Motti

Unveränderlichkeit ist gut. Siehe Effektives Java. Wenn Sie einen String jedes Mal kopieren müssten, wenn Sie ihn weitergeben, wäre das eine Menge fehleranfälliger Code. Sie haben auch Unklarheiten darüber, welche Änderungen sich auf welche Referenzen auswirken. Ebenso wie Integer unveränderlich sein muss, um sich wie Int zu verhalten, müssen Strings unveränderlich sein, um sich wie Primitive zu verhalten. In C++ führt das Übergeben von Zeichenfolgen nach Wert dazu, dass dies im Quellcode nicht explizit erwähnt wird.

Es gibt eine Ausnahme für fast jede Regel:

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