Warum ist String in Java unveränderlich?


78

Ich konnte den Grund dafür nicht verstehen. Ich verwende die String-Klasse immer wie andere Entwickler, aber wenn ich den Wert ändere, wird eine neue Instanz von String erstellt.

Was könnte der Grund für die Unveränderlichkeit der String-Klasse in Java sein?

Ich weiß, dass es einige Alternativen wie StringBuffer oder StringBuilder gibt. Es ist nur Neugier.


20
Technisch gesehen ist es kein Duplikat, aber Eric Lippert gibt hier eine großartige Antwort auf diese Frage: programmers.stackexchange.com/a/190913/33843
Heinzi

Antworten:


105

Parallelität

Java wurde von Anfang an unter Berücksichtigung der Parallelität definiert. Wie schon oft erwähnt, sind geteilte Variablen problematisch. Eine Sache kann eine andere hinter dem Rücken eines anderen Threads ändern, ohne dass dieser Thread davon Kenntnis hat.

Es gibt eine Vielzahl von Multithread-C ++ - Fehlern, die aufgrund eines gemeinsam genutzten Strings aufgetreten sind. Ein Modul hielt es für sicher, Änderungen vorzunehmen, wenn ein anderes Modul im Code einen Zeiger darauf gespeichert hatte und erwartete, dass dieser gleich bleibt.

Die "Lösung" für dieses Problem besteht darin, dass jede Klasse eine defensive Kopie der veränderlichen Objekte erstellt, die an sie übergeben werden. Für veränderbare Zeichenfolgen ist dies O (n), um die Kopie zu erstellen. Bei unveränderlichen Zeichenfolgen ist das Erstellen einer Kopie O (1), da es sich nicht um eine Kopie handelt, sondern um dasselbe Objekt, das nicht geändert werden kann.

In einer Multithread-Umgebung können unveränderliche Objekte immer sicher miteinander geteilt werden. Dies führt insgesamt zu einer Verringerung der Speichernutzung und verbessert das Zwischenspeichern von Speicher.

Sicherheit

Oft werden Zeichenfolgen als Argumente an Konstrukteure weitergegeben - Netzwerkverbindungen und Protokolle sind die beiden, die am einfachsten in den Sinn kommen. Die Möglichkeit, dies zu einem unbestimmten Zeitpunkt später in der Ausführung zu ändern, kann zu Sicherheitsproblemen führen (die Funktion glaubte, eine Verbindung zu einem Computer herzustellen, wurde jedoch zu einem anderen umgeleitet, aber alles im Objekt scheint mit dem ersten verbunden zu sein). es ist sogar die gleiche Zeichenfolge).

In Java kann man Reflection verwenden - und die Parameter dafür sind Strings. Die Gefahr, dass eine Zeichenfolge übergeben wird, die auf dem Weg zu einer anderen reflektierenden Methode geändert werden kann. Das ist sehr schlecht.

Schlüssel zum Hash

Die Hash-Tabelle ist eine der am häufigsten verwendeten Datenstrukturen. Die Schlüssel zur Datenstruktur sind sehr oft Zeichenfolgen. Unveränderliche Zeichenfolgen bedeuten, dass (wie oben) die Hash-Tabelle nicht jedes Mal eine Kopie des Hash-Schlüssels erstellen muss. Wenn Strings veränderbar wären und die Hash-Tabelle dies nicht schafft, könnte der Hash-Schlüssel in einiger Entfernung geändert werden.

Die Art und Weise, wie das Objekt in Java funktioniert, ist, dass alles einen Hash-Schlüssel hat (auf den über die Methode hashCode () zugegriffen wird). Ein unveränderlicher String bedeutet, dass der Hashcode zwischengespeichert werden kann. In Anbetracht der Häufigkeit, mit der Zeichenfolgen als Schlüssel für einen Hash verwendet werden, wird die Leistung erheblich gesteigert (anstatt dass der Hashcode jedes Mal neu berechnet werden muss).

Teilstrings

Indem der String unveränderlich ist, ist auch das zugrunde liegende Zeichenarray, das die Datenstruktur unterstützt, unveränderlich. Dadurch können bestimmte Optimierungen an der substringMethode vorgenommen werden (dies ist nicht unbedingt der Fall - es können auch Speicherverluste auftreten).

Wenn Sie tun:

String foo = "smiles";
String bar = foo.substring(1,5);

Der Wert von barist 'Meile'. Beide foound barkönnen jedoch durch dasselbe Zeichenarray gesichert werden, wodurch die Instanziierung von mehr Zeichenarrays reduziert oder kopiert wird - nur mit unterschiedlichen Start- und Endpunkten in der Zeichenfolge.

foo | | (0, 6)
    vv
    lächelt
     ^^
bar | | (fünfzehn)

Der Nachteil davon (der Speicherverlust) ist, dass, wenn man eine 1k lange Zeichenfolge hätte und die Teilzeichenfolge des ersten und zweiten Zeichens nehmen würde, diese ebenfalls durch das 1k lange Zeichenarray unterstützt würde. Dieses Array würde auch dann im Speicher verbleiben, wenn die ursprüngliche Zeichenfolge, die den Wert des gesamten Zeichen-Arrays enthielt, fehlerhaft erfasst worden wäre.

Dies ist in String aus JDK 6b14 zu sehen (der folgende Code stammt aus einer GPL v2-Quelle und wird als Beispiel verwendet).

   public String(char value[], int offset, int count) {
       if (offset < 0) {
           throw new StringIndexOutOfBoundsException(offset);
       }
       if (count < 0) {
           throw new StringIndexOutOfBoundsException(count);
       }
       // Note: offset or count might be near -1>>>1.
       if (offset > value.length - count) {
           throw new StringIndexOutOfBoundsException(offset + count);
       }
       this.offset = 0;
       this.count = count;
       this.value = Arrays.copyOfRange(value, offset, offset+count);
   }

   // Package private constructor which shares value array for speed.
   String(int offset, int count, char value[]) {
       this.value = value;
       this.offset = offset;
       this.count = count;
   }

   public String substring(int beginIndex, int endIndex) {
       if (beginIndex < 0) {
           throw new StringIndexOutOfBoundsException(beginIndex);
       }
       if (endIndex > count) {
           throw new StringIndexOutOfBoundsException(endIndex);
       }
       if (beginIndex > endIndex) {
           throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
       }
       return ((beginIndex == 0) && (endIndex == count)) ? this :
           new String(offset + beginIndex, endIndex - beginIndex, value);
   }

Beachten Sie, dass die Teilzeichenfolge den String-Konstruktor auf Paketebene verwendet, bei dem das Array nicht kopiert werden muss und der viel schneller ist (möglicherweise auf Kosten einiger großer Arrays - obwohl auch keine großen Arrays dupliziert werden).

Beachten Sie, dass der obige Code für Java 1.6 ist. Die Art und Weise, wie der Konstruktor der Teilzeichenfolge implementiert wird, wurde mit Java 1.7 geändert ( siehe Änderungen an der internen Darstellung von Zeichenfolgen in Java 1.7.0_06) . Java wurde wahrscheinlich nicht als eine Sprache mit vielen Zeichenfolgenmanipulationen angesehen, und daher war die Leistungssteigerung für eine Teilzeichenfolge eine gute Sache. Bei riesigen XML-Dokumenten, die in Strings gespeichert sind, die niemals erfasst werden, wird dies zu einem Problem. StringDaher wird die Verwendung desselben zugrunde liegenden Arrays mit einer Teilzeichenfolge geändert , sodass das größere Zeichenarray schneller erfasst werden kann.

Missbrauche den Stapel nicht

Man könnte den Wert der Zeichenfolge anstelle des Verweises auf die unveränderliche Zeichenfolge übergeben, um Probleme mit der Veränderbarkeit zu vermeiden. Wenn Sie jedoch große Zeichenfolgen auf dem Stapel ablegen, wird dies dem System missbräuchlich. (Legen Sie ganze XML-Dokumente als Zeichenfolgen auf den Stapel und nehmen Sie sie dann ab oder leiten Sie sie weiter.)

Die Möglichkeit der Deduplizierung

Zugegeben, dies war keine anfängliche Motivation dafür, warum Strings unveränderlich sein sollten, aber wenn man sich die Gründe dafür ansieht, warum unveränderliche Strings eine gute Sache sind, sollte man dies mit Sicherheit berücksichtigen.

Jeder, der ein bisschen mit Strings gearbeitet hat, weiß, dass er Erinnerungen lutschen kann. Dies gilt insbesondere dann, wenn Sie Daten aus Datenbanken abrufen, die eine Weile in der Nähe bleiben. Oftmals sind diese Stiche immer wieder dieselbe Saite (einmal für jede Reihe).

Viele große Java-Anwendungen haben derzeit einen Engpass im Arbeitsspeicher. Messungen haben ergeben, dass ungefähr 25% des Java-Heap-Livedatensatzes in diesen Anwendungstypen von String-Objekten verbraucht wird. Etwa die Hälfte dieser String-Objekte sind Duplikate, wobei Duplikate bedeuten, dass string1.equals (string2) wahr ist. Doppelte String-Objekte auf dem Heap zu haben, ist im Wesentlichen nur eine Verschwendung von Speicher. ...

Mit Java 8 Update 20 wird JEP 192 (oben zitierte Motivation) implementiert, um dies zu beheben. Ohne näher auf die Funktionsweise der String-Deduplizierung einzugehen, ist es wichtig, dass die Strings selbst unveränderlich sind. Sie können StringBuilder nicht deduplizieren, da sie sich ändern können und Sie nicht möchten, dass jemand etwas unter Ihnen ändert. Unveränderliche Zeichenfolgen (im Zusammenhang mit diesem Zeichenfolgenpool) bedeuten, dass Sie durchgehen können. Wenn Sie zwei identische Zeichenfolgen finden, können Sie eine Zeichenfolgenreferenz auf die andere verweisen und den Garbage Collector die neu nicht verwendete Zeichenfolge verwenden lassen.

Andere Sprachen

Ziel C (welches vor Java liegt) hat NSStringund NSMutableString.

C # und .NET haben dieselbe Entwurfsauswahl getroffen, bei der die Standardzeichenfolge unveränderlich ist.

Lua- Saiten sind ebenfalls unveränderlich.

Python auch.

In der Vergangenheit haben Lisp, Scheme, Smalltalk alle die Zeichenfolge interniert und müssen sie daher unveränderlich sein. Modernere dynamische Sprachen verwenden oft Strings in irgendeiner Art und Weise, dass sie sein unveränderlich erfordert (es ist nicht ein sein kann String , aber es ist unveränderlich).

Fazit

Diese gestalterischen Überlegungen wurden immer wieder in einer Vielzahl von Sprachen angestellt. Es besteht allgemeiner Konsens darüber, dass unveränderliche Zeichenfolgen trotz aller Ungeschicklichkeit besser sind als die Alternativen und zu besserem Code (weniger Bugs) und insgesamt schnelleren ausführbaren Dateien führen.


3
Java bietet veränderbare und unveränderliche Zeichenfolgen. In dieser Antwort werden einige Leistungsvorteile beschrieben, die mit unveränderlichen Zeichenfolgen erzielt werden können, und einige Gründe, aus denen unveränderliche Daten ausgewählt werden können. Es wird jedoch nicht erläutert, warum die unveränderliche Version die Standardversion ist.
Billy ONeal

3
@BillyONeal: Ein sicherer Standard und eine unsichere Alternative führen fast immer zu sichereren Systemen als der umgekehrte Ansatz.
Joachim Sauer

4
@BillyONeal Wenn die unveränderliche Datei nicht die Standardeinstellung wäre, würden die Probleme von Parallelität, Sicherheit und Hashes häufiger auftreten. Die Sprachentwickler haben (teilweise als Reaktion auf C) eine Sprache ausgewählt, in der die Standardeinstellungen festgelegt wurden, um eine Reihe von häufigen Fehlern zu vermeiden und die Effizienz der Programmierer zu verbessern (ohne sich mehr um diese Fehler sorgen zu müssen). Es gibt weniger (offensichtliche und versteckte) Fehler bei unveränderlichen Zeichenfolgen als bei veränderlichen.

@ Joachim: Ich behaupte nicht anders.
Billy ONeal

1
Technisch gesehen hat Common Lisp veränderbare Zeichenfolgen für "zeichenfolgenähnliche" Operationen und Symbole mit unveränderlichen Namen für unveränderliche Bezeichner.
Vatine

21

Gründe, an die ich mich erinnern kann:

  1. Eine String-Pool-Funktion, ohne den String unveränderlich zu machen, ist überhaupt nicht möglich, da im Falle eines String-Pools ein String-Objekt / Literal, z. B. "XYZ", von vielen Referenzvariablen referenziert wird. Wenn sich also einer von ihnen ändert, wird der Wert der anderen automatisch beeinflusst .

  2. Zeichenfolge wird häufig als Parameter für viele Java-Klassen verwendet, z. B. zum Öffnen der Netzwerkverbindung, zum Öffnen der Datenbankverbindung und zum Öffnen von Dateien. Wenn String nicht unveränderlich ist, würde dies zu einer ernsthaften Sicherheitsbedrohung führen.

  3. Durch die Unveränderlichkeit kann String seinen Hashcode zwischenspeichern.

  4. Macht es threadsicher.


7

1) String Pool

Java-Designer wissen, dass String in allen Arten von Java-Anwendungen der am häufigsten verwendete Datentyp sein wird, und deshalb wollten sie von Anfang an optimieren. Ein wichtiger Schritt in diese Richtung war die Idee, String-Literale im String-Pool zu speichern. Ziel war es, temporäre String-Objekte zu reduzieren, indem sie gemeinsam genutzt werden. Um sie gemeinsam nutzen zu können, müssen sie aus der Klasse Unveränderlich stammen. Sie können ein veränderbares Objekt nicht mit zwei unbekannten Parteien teilen. Nehmen wir ein hypothetisches Beispiel, in dem zwei Referenzvariablen auf dasselbe String-Objekt zeigen:

String s1 = "Java";
String s2 = "Java";

Wenn nun s1 das Objekt von "Java" in "C ++" ändert, hat die Referenzvariable auch den Wert s2 = "C ++", von dem sie nichts weiß. Indem String unveränderlich gemacht wurde, war dieses Teilen von String-Literal möglich. Kurz gesagt, die Schlüsselidee des String-Pools kann nicht implementiert werden, ohne String final oder Unveränderlich in Java zu machen.

2) Sicherheit

Java hat ein klares Ziel in Bezug auf die Bereitstellung einer sicheren Umgebung auf jeder Serviceebene, und String ist für diese gesamten Sicherheitsaspekte von entscheidender Bedeutung. String wurde häufig als Parameter für viele Java-Klassen verwendet, z. B. zum Öffnen der Netzwerkverbindung können Sie Host und Port als String übergeben, zum Lesen von Dateien in Java können Sie den Pfad von Dateien und das Verzeichnis als String übergeben und zum Öffnen der Datenbankverbindung Datenbank-URL als String übergeben. Wenn String nicht unveränderlich war, hat ein Benutzer möglicherweise Zugriff auf eine bestimmte Datei im System gewährt. Nach der Authentifizierung kann er den Pfad jedoch in einen anderen ändern. Dies kann schwerwiegende Sicherheitsprobleme verursachen. Während der Verbindung mit der Datenbank oder einem anderen Computer im Netzwerk kann die Änderung des String-Werts ebenfalls Sicherheitsrisiken mit sich bringen. Veränderbare Zeichenfolgen können auch in Reflection Sicherheitsprobleme verursachen.

3) Verwendung von Fäden im Klassenlademechanismus

Ein weiterer Grund dafür, String final oder Immutable zu machen, war die Tatsache, dass er in Klassenlademechanismen häufig verwendet wurde. Da String nicht unveränderlich ist, kann ein Angreifer diese Tatsache ausnutzen und eine Anforderung zum Laden von Java-Standardklassen, z. B. java.io.Reader, in die böswillige Klasse com.unknown.DataStolenReader ändern. Indem wir String final und unveränderlich halten, können wir zumindest sicher sein, dass JVM die richtigen Klassen lädt.

4) Multithreading-Vorteile

Da Concurrency und Multi-Threading das Hauptangebot von Java waren, war es sehr sinnvoll, über die Thread-Sicherheit von String-Objekten nachzudenken. Da erwartet wurde, dass String in großem Umfang verwendet wird, bedeutet Unveränderlich keine externe Synchronisation, viel saubereren Code, der das Teilen von String zwischen mehreren Threads beinhaltet. Diese einzige Funktion erleichtert die komplizierte, verwirrende und fehleranfällige Parallelitätscodierung erheblich. Da String unveränderlich ist und nur zwischen Threads geteilt wird, führt dies zu besser lesbarem Code.

5) Optimierung und Leistung

Wenn Sie eine Klasse zu Unveränderlich machen, wissen Sie im Voraus, dass sich diese Klasse nach ihrer Erstellung nicht ändern wird. Dies garantiert einen offenen Pfad für viele Leistungsoptimierungen, z. B. Caching. String selbst weiß, dass ich nicht ändern werde, also zwischenspeichern String seinen Hashcode. Es berechnet sogar den Hashcode träge und sobald er erstellt wurde, kann er zwischengespeichert werden. Wenn Sie in einer einfachen Welt die Methode hashCode () eines beliebigen String-Objekts zum ersten Mal aufrufen, wird der Hash-Code berechnet, und alle nachfolgenden Aufrufe von hashCode () geben bereits berechnete, zwischengespeicherte Werte zurück. Dies führt zu einer guten Leistungssteigerung, da String in Hash-basierten Maps, z. B. Hashtable und HashMap, häufig verwendet wird. Das Zwischenspeichern von Hashcode war nicht möglich, ohne ihn unveränderlich und endgültig zu machen, da dies vom Inhalt von String selbst abhängt.


5

Die Java Virtual Machine führt verschiedene Optimierungen in Bezug auf Zeichenfolgenoperationen durch, die sonst nicht ausgeführt werden könnten. Wenn Sie beispielsweise eine Zeichenfolge mit dem Wert "Mississippi" hatten und einer anderen Zeichenfolge "Mississippi" .substring (0, 4) zugewiesen haben, wurde, soweit Sie wissen, eine Kopie der ersten vier Zeichen erstellt, um "Miss" zu erstellen. . Was Sie nicht wissen, ist, dass beide dieselbe ursprüngliche Zeichenfolge "Mississippi" gemeinsam haben, wobei die eine der Eigentümer ist und die andere eine Referenz dieser Zeichenfolge von Position 0 bis 4. (Die Referenz auf den Eigentümer verhindert, dass der Eigentümer von gesammelt wird der Müllsammler, wenn der Besitzer den Rahmen verlässt)

Dies ist trivial für eine Saite, die so klein wie "Mississippi" ist, aber bei größeren Saiten und mehreren Operationen ist es eine große Zeitersparnis, die Saite nicht kopieren zu müssen! Wenn Strings veränderbar wären, könnten Sie dies nicht tun, da das Ändern des Originals auch die "Kopien" des Teilstrings beeinflussen würde.

Außerdem würde, wie Donal erwähnt, der Vorteil durch seinen Nachteil stark beeinträchtigt. Stellen Sie sich vor, Sie schreiben ein Programm, das von einer Bibliothek abhängt, und Sie verwenden eine Funktion, die einen String zurückgibt. Wie können Sie sicher sein, dass dieser Wert konstant bleibt? Damit so etwas nicht passiert, müssten Sie immer eine Kopie vorlegen.

Was ist, wenn Sie zwei Threads haben, die dieselbe Zeichenfolge verwenden? Sie möchten nicht eine Zeichenfolge lesen, die gerade von einem anderen Thread umgeschrieben wird, oder? String müsste dann Thread-sicher sein, was die übliche Klasse ist, die praktisch jedes Java-Programm so viel langsamer macht. Andernfalls müssten Sie für jeden Thread, für den diese Zeichenfolge erforderlich ist, eine Kopie erstellen, oder Sie müssten den Code mithilfe dieser Zeichenfolge in einen Synchronisierungsblock einfügen, was beides Ihr Programm nur verlangsamt.

Aus all diesen Gründen war dies eine der ersten Entscheidungen, die für Java getroffen wurden, um sich von C ++ abzuheben.


Theoretisch können Sie eine mehrschichtige Pufferverwaltung durchführen, die das Kopieren bei Mutation ermöglicht, wenn diese gemeinsam genutzt wird. In einer Umgebung mit mehreren Threads ist es jedoch sehr schwierig, die Arbeit effizient zu gestalten.
Donal Fellows

@DonalFellows Ich bin nur davon ausgegangen, dass die Java Virtual Machine (offensichtlich) nicht in Java geschrieben ist, sondern intern mit gemeinsamen Zeigern oder ähnlichem verwaltet wird.
Neil

5

Der Grund für die Unveränderlichkeit der Zeichenfolge liegt in der Übereinstimmung mit anderen primitiven Typen in der Sprache. Wenn Sie eine haben, intdie den Wert 42 enthält, und den Wert 1 hinzufügen, ändern Sie die 42 nicht. Sie erhalten einen neuen Wert, 43, der völlig unabhängig von den Startwerten ist. Andere Grundelemente als Zeichenfolgen zu mutieren, ergibt keinen begrifflichen Sinn. und als solche sind Programme, die Zeichenfolgen als unveränderlich behandeln, oft einfacher zu überlegen und zu verstehen.

Darüber hinaus bietet Java wirklich sowohl veränderbare als auch unveränderliche Zeichenfolgen, wie Sie sehen StringBuilder. Eigentlich ist nur die unveränderliche Zeichenfolge die Standardeinstellung . Wenn Sie StringBuilderüberall Referenzen weitergeben möchten, können Sie dies gerne tun. Java verwendet für diese Konzepte separate Typen ( Stringund StringBuilder), da es keine Unterstützung für das Ausdrücken von Veränderlichkeit oder deren Fehlen in seinem Typsystem gibt. In Sprachen, die Unveränderlichkeit in ihren Typsystemen unterstützen (z. B. C ++ const), gibt es häufig einen einzelnen Zeichenfolgentyp, der beiden Zwecken dient.

Ja, wenn Zeichenfolgen unveränderlich sind, können einige Optimierungen für unveränderliche Zeichenfolgen implementiert werden, z. B. Internierung. Außerdem können Zeichenfolgenreferenzen ohne Synchronisierung zwischen Threads weitergegeben werden. Dies verwechselt jedoch den Mechanismus mit dem angestrebten Ziel einer Sprache mit einem einfachen und konsistenten Typensystem. Ich vergleiche das damit, wie jeder falsch über Müllsammlung denkt; Speicherbereinigung ist nicht "Rückgewinnung von nicht verwendetem Speicher"; Es wird "ein Computer mit unbegrenztem Speicher simuliert" . Die besprochenen Leistungsoptimierungen dienen dazu, dass das Ziel unveränderlicher Zeichenfolgen auf realen Maschinen eine gute Leistung erbringt. nicht der Grund, warum solche Saiten überhaupt unveränderlich sind.


@ Billy-Oneal .. In Bezug auf "Wenn Sie ein Int mit dem Wert 42 haben und den Wert 1 hinzufügen, ändern Sie den Wert 42 nicht. Sie erhalten einen neuen Wert 43, der in keinerlei Beziehung zum Start steht Werte." Bist du dir da sicher?
Shamit Verma

@Shamit: Ja, ich bin mir sicher. Das Hinzufügen von 1 zu 42 führt zu 43. Die Zahl 42 bedeutet nicht dasselbe wie die Zahl 43.
Billy ONeal

@Shamit: In ähnlicher Weise können Sie nicht so etwas tun 43 = 6und erwarten, dass die Zahl 43 dasselbe wie die Zahl 6 bedeutet.
Billy ONeal

int i = 42; i = i + 1; Dieser Code speichert 42 im Speicher und ändert dann die Werte an derselben Stelle in 43. Tatsächlich erhält die Variable "i" den neuen Wert 43.
Shamit Verma

@Shamit: In diesem Fall haben Sie mutiert i, nicht 42. Überlegen Sie string s = "Hello "; s += "World";. Sie haben den Wert der Variablen geändert s. Aber die Saiten "Hello ", "World"und "Hello World"unveränderlich sind.
Billy ONeal

4

Unveränderlichkeit bedeutet, dass Konstanten von Klassen, die Sie nicht besitzen, nicht geändert werden können. Zu den Klassen, die Sie nicht besitzen, gehören diejenigen, die sich im Kern der Java-Implementierung befinden, und zu den Zeichenfolgen, die nicht geändert werden sollten, gehören Sicherheitstoken, Dienstadressen usw. Sie sollten diese Sortierungen wirklich nicht ändern können von Dingen (und dies gilt doppelt, wenn im Sandkasten-Modus gearbeitet wird).

Wenn String nicht unveränderlich ist, müssen Sie jedes Mal, wenn Sie ihn aus einem Kontext abrufen, in dem der Inhalt des Strings nicht unter den Füßen geändert werden soll, eine Kopie "nur für den Fall" erstellen. Das wird sehr teuer.


4
Das gleiche Argument gilt für jeden Typ, nicht nur für String. Aber zum Beispiel sind Arrays trotzdem veränderlich. Also, warum sind Strings unveränderlich und Arrays nicht. Und wenn Unveränderlichkeit so wichtig ist, warum macht es Java dann so schwierig, unveränderliche Objekte zu erstellen und damit zu arbeiten?
Jörg W Mittag

1
@ JörgWMittag: Ich nehme an, das ist im Grunde genommen eine Frage der Radikalität, die sie haben wollten. Einen unveränderlichen String zu haben, war in den Tagen von Java 1.0 ziemlich radikal. Ein (hauptsächlich oder sogar ausschließlich) unveränderliches Sammlungsgerüst zu haben, könnte zu radikal gewesen sein, um die Sprache umfassend zu nutzen.
Joachim Sauer

Es ist ziemlich schwierig, ein effektives Framework für unveränderliche Sammlungen zu erstellen, damit es performant wird, wenn man als jemand spricht, der so etwas geschrieben hat (aber nicht in Java). Ich wünsche mir auch total, dass ich unveränderliche Arrays hätte; das hätte mir einiges an arbeit erspart.
Donal Fellows

@DonalFellows: pcollections zielt darauf ab, genau das zu tun (ich habe es jedoch nie benutzt).
Joachim Sauer

3
@ JörgWMittag: Es gibt Leute (üblicherweise aus rein funktionaler Sicht), die argumentieren würden, dass alle Typen unveränderlich sein sollten. Ebenso denke ich, dass, wenn Sie alle Probleme zusammenfassen, die mit der Arbeit mit veränderlichem Status in paralleler und gleichzeitiger Software zu tun haben, Sie vielleicht zustimmen, dass die Arbeit mit unveränderlichen Objekten oft viel einfacher ist als mit veränderlichen.
Steven Evers

2

Stellen Sie sich ein System vor, in dem Sie einige Daten akzeptieren, ihre Richtigkeit überprüfen und dann weitergeben (um sie beispielsweise in einer Datenbank zu speichern).

Angenommen, die Daten sind a Stringund müssen mindestens 5 Zeichen lang sein. Ihre Methode sieht ungefähr so ​​aus:

public void handle(String input) {
  if (input.length() < 5) {
    throw new IllegalArgumentException();
  }
  storeInDatabase(input);
}

Jetzt können wir uns darauf einigen, dass, wenn storeInDatabasehier aufgerufen inputwird, die Anforderungen erfüllt werden. Aber wenn Stringwandelbar waren, dann wird der Anrufer konnte verändert das inputObjekt (von einem anderen Thread) direkt nach dem festgestellt wurde , und bevor es wurde in der Datenbank gespeichert . Dies würde ein gutes Timing erfordern und wahrscheinlich nicht jedes Mal gut gehen, aber gelegentlich kann er Sie dazu bringen, ungültige Werte in der Datenbank zu speichern.

Unveränderliche Datentypen sind eine sehr einfach Lösung für dieses Problem (und viele ähnliche) Probleme: wenn Sie einen gewissen Wert überprüfen, können Sie abhängig von der Tatsache , dass die überprüfte Bedingung später noch wahr ist.


Danke für die Erklärung. Was ist, wenn ich so eine Handle-Methode aufrufe? handle (neuer String (Eingabe + "naberlan")). Ich denke, ich kann ungültige Werte in der Datenbank wie folgt speichern.
Yfklon

1
@blank: naja, da der inputvon der handlemethode schon zu lang ist (egal was das original input ist), würde es einfach eine ausnahme auslösen . Sie erstellen eine neue Eingabe, bevor Sie die Methode aufrufen. Das ist kein Problem.
Joachim Sauer

0

Im Allgemeinen werden Sie auf Werttypen und Referenztypen stoßen . Bei einem Wertetyp kümmert es Sie nicht um das Objekt, das ihn darstellt, sondern um den Wert. Wenn ich Ihnen einen Wert gebe, erwarten Sie, dass dieser Wert derselbe bleibt. Du willst nicht, dass es sich plötzlich ändert. Die Nummer 5 ist ein Wert. Sie erwarten nicht, dass es sich plötzlich auf 6 ändert. Die Zeichenfolge "Hallo" ist ein Wert. Sie erwarten nicht, dass es plötzlich auf "P *** off" wechselt.

Bei Referenztypen interessiert Sie das Objekt und Sie erwarten, dass es sich ändert. Beispielsweise werden Sie häufig erwarten, dass sich ein Array ändert. Wenn ich Ihnen ein Array gebe und Sie möchten, dass es genau so bleibt, wie es ist, müssen Sie mir entweder vertrauen, dass ich es nicht ändere, oder Sie erstellen eine Kopie davon.

Bei der Java-String-Klasse mussten die Designer eine Entscheidung treffen: Ist es besser, wenn sich Strings wie ein Werttyp verhalten, oder sollten sie sich wie ein Referenztyp verhalten? Bei Java-Strings wurde die Entscheidung getroffen, dass es sich um Werttypen handeln soll. Da es sich also um Objekte handelt, müssen es sich um unveränderliche Objekte handeln.

Die gegenteilige Entscheidung hätte getroffen werden können, aber meiner Meinung nach hätte dies viele Kopfschmerzen verursacht. Wie bereits an anderer Stelle erwähnt, haben viele Sprachen dieselbe Entscheidung getroffen und sind zu demselben Ergebnis gekommen. Eine Ausnahme ist C ++ mit einer Zeichenfolgenklasse. Zeichenfolgen können konstant oder nicht konstant sein. In C ++ können Objektparameter im Gegensatz zu Java jedoch als Werte und nicht als Referenzen übergeben werden.


0

Ich bin wirklich überrascht, dass niemand darauf hingewiesen hat.

Antwort: Es würde Ihnen keinen nennenswerten Nutzen bringen, selbst wenn es veränderlich wäre. Es würde Ihnen nicht so viel nützen, da dies zusätzliche Probleme verursacht. Untersuchen wir zwei der häufigsten Mutationsfälle:

Ändern eines Zeichens einer Zeichenfolge

Da jedes Zeichen in einer Java-Zeichenfolge entweder 2 oder 4 Bytes benötigt, fragen Sie sich, ob Sie etwas gewinnen würden, wenn Sie die vorhandene Kopie mutieren könnten.

In dem Szenario, in dem Sie ein 2-Byte-Zeichen durch ein 4-Byte-Zeichen ersetzen (oder umgekehrt), müssen Sie den verbleibenden Teil der Zeichenfolge um 2 Byte nach links oder rechts verschieben. Das ist nicht anders als das Kopieren des gesamten Strings aus rechnerischer Sicht.

Dies ist auch ein sehr unregelmäßiges Verhalten, das in der Regel unerwünscht ist. Stellen Sie sich vor, jemand testet eine Anwendung mit englischem Text, und wenn die Anwendung in einem anderen Land wie China eingeführt wird, funktioniert das Ganze merkwürdig.

Anhängen einer anderen Zeichenfolge (oder eines anderen Zeichens) an die vorhandene Zeichenfolge

Wenn Sie zwei beliebige Zeichenfolgen haben, befinden sich diese an zwei unterschiedlichen Speicherorten. Wenn Sie den ersten ändern möchten, indem Sie den zweiten anhängen, können Sie nicht einfach zusätzlichen Speicher am Ende des ersten Strings anfordern, da dieser wahrscheinlich bereits belegt ist.

Sie müssen die verkettete Zeichenfolge an eine ganz neue Position kopieren. Dies ist genauso, als ob beide Zeichenfolgen unveränderlich wären.

Wenn Sie das Anhängen effizient durchführen möchten, können Sie das Programm verwenden StringBuilder, das am Ende einer Zeichenfolge ziemlich viel Speicherplatz reserviert, und zwar nur für diesen Zweck eines möglichen zukünftigen Anhängens.


-2
  1. Sie sind teuer und können unveränderlich bleiben, beispielsweise wenn Unterzeichenfolgen das Byte-Array der Hauptzeichenfolge gemeinsam nutzen. (Geschwindigkeitssteigerung auch, da kein neues Byte-Array erstellt und kopiert werden muss)

  2. Sicherheit - möchte nicht, dass Ihr Paket oder Klassencode umbenannt wird

    [entfernte alte 3 sah sich StringBuilder src an - es teilt sich keinen Speicher mit String (bis geändert) Ich denke, das war in 1.3 oder 1.4]

  3. Cache-Hashcode

  4. für mutalble Strings verwenden Sie SB (Builder oder Buffer nach Bedarf)


2
1. Natürlich gibt es die Strafe, die größeren Teile der Saite nicht zerstören zu können, wenn dies passiert. Das Internieren ist nicht kostenlos; Dies verbessert jedoch die Leistung vieler realer Programme. 2. Es könnte leicht "string" und "ImmutableString" geben, die diese Anforderung erfüllen könnten. 3. Ich bin nicht sicher, ob ich das verstehe ...
Billy ONeal

.3. hätte den hash code zwischenspeichern sollen. Auch dies könnte mit einer veränderlichen Zeichenfolge erfolgen. @ billy-oneal
tgkprog

-4

Strings sollten in Java ein primitiver Datentyp sein. Wenn dies der Fall gewesen wäre, wären die Zeichenfolgen standardmäßig veränderbar, und das letzte Schlüsselwort würde unveränderliche Zeichenfolgen generieren. Veränderbare Zeichenfolgen sind nützlich, und daher gibt es mehrere Hacks für veränderbare Zeichenfolgen in den Klassen stringbuffer, stringbuilder und charsequence.


3
Dies beantwortet nicht den "Warum" Aspekt dessen, was es jetzt ist, was die Frage stellt. Außerdem funktioniert Java Final nicht so. Mutable Strings sind keine Hacks, sondern echte Entwurfsüberlegungen, die auf den häufigsten Verwendungen von Strings und den Optimierungen beruhen, die zur Verbesserung des JVM vorgenommen werden können.

1
Die Antwort auf das "Warum" ist eine schlechte Entscheidung für das Sprachdesign. Der Compiler / die JVM sollte mit drei leicht unterschiedlichen Methoden arbeiten, um veränderbare Zeichenfolgen zu unterstützen.
CWallach

3
String und StringBuffer waren das Original. StringBuilder wurde später hinzugefügt und erkannte eine Designschwierigkeit mit StringBuffer. Veränderbare und unveränderbare Zeichenfolgen, die unterschiedliche Objekte sind, sind in vielen Sprachen zu finden, da das Design immer wieder berücksichtigt und entschieden wurde, dass es sich jeweils um unterschiedliche Objekte handelt. C # "Strings sind unveränderlich" und Warum ist .NET String unveränderlich? , Objective C NSString ist unveränderlich, während NSMutableString veränderlich ist. stackoverflow.com/questions/9544182
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.