Warum können Zeichenfolgen in Java und .NET nicht veränderbar sein?
189
Warum haben sie beschlossen, Stringin Java und .NET (und einigen anderen Sprachen) unveränderlich zu machen ? Warum haben sie es nicht veränderlich gemacht?
Ich hatte den gleichen Gedanken, überprüfte aber die Position der Originalplakate und stellte fest, dass sie aus Belgien stammen. Angesichts dessen bedeutet dies, dass sie wahrscheinlich kein englischer Muttersprachler sind. In Verbindung mit der Tatsache, dass die meisten Eingeborenen die Sprache nicht verstehen, beschloss ich, sie etwas zu lockern.
Belugabob
8
Danke Belugabob, aber ich bin keine sie, ich bin ein er. Anscheinend berücksichtigen die Menschen die kulturellen Unterschiede nicht.
chrissie1
7
Ich entschuldige mich - chrissie ist (im Allgemeinen) der Name eines Mädchens in Großbritannien - und mache mich zum Opfer eines anderen kulturellen Unterschieds :-)
"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 Klasseninvarianten erstellen, ist garantiert, dass diese Invarianten für alle Zeiten mit wahr bleiben keine Anstrengung von Ihrer Seite.
[...]
Unveränderliche Objekte sind von Natur aus threadsicher. Sie erfordern keine Synchronisation. Sie können nicht durch mehrere Threads beschädigt werden, die gleichzeitig auf sie zugreifen. Dies ist bei weitem der einfachste Ansatz, um die Gewindesicherheit 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ßartige Bausteine für andere Objekte, ob veränderlich oder unveränderlich.
[...]
Der einzige wirkliche Nachteil unveränderlicher Klassen besteht darin, dass sie für jeden einzelnen Wert ein separates Objekt benötigen.
Lesen Sie den zweiten Satz meiner Antwort: Unveränderliche Klassen sind einfacher zu entwerfen, zu implementieren und zu verwenden als veränderbare Klassen. Sie sind weniger fehleranfällig und sicherer.
PRINZESSIN FLUFF
5
@PRINCESSFLUFF Ich würde hinzufügen, dass das Teilen von veränderlichen Zeichenfolgen selbst in einem einzelnen Thread gefährlich ist. Beispiel: Kopieren eines Berichts : report2.Text = report1.Text;. Dann, woanders, den Text ändern : report2.Text.Replace(someWord, someOtherWord);. Dies würde sowohl den ersten als auch den zweiten Bericht ändern.
Phoog
10
@Sam fragte er nicht "warum sie nicht veränderlich sein können", er fragte "warum sie beschlossen, unveränderlich zu machen", was dies perfekt beantwortet.
James
1
@PRINCESSFLUFF Diese Antwort befasst sich nicht speziell mit Zeichenfolgen. Das war die Frage der OP. Es ist so frustrierend - das passiert die ganze Zeit auf SO und auch bei Fragen zur Unveränderlichkeit von Strings. Die Antwort hier spricht über die allgemeinen Vorteile der Unveränderlichkeit. Warum sind dann nicht alle Typen unveränderlich? Können Sie bitte zurückgehen und String ansprechen?
Howiecamp
@Howiecamp Ich denke, die Antwort impliziert, dass Zeichenfolgen veränderbar gewesen sein könnten (nichts hindert eine hypothetische veränderbare Zeichenfolgenklasse daran, vorhanden zu sein). Sie haben sich einfach der Einfachheit halber entschieden, dies nicht so zu tun, und weil es die 99% der Anwendungsfälle abdeckte. Für die anderen 1% -Fälle wurde weiterhin StringBuilder bereitgestellt.
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 Anmeldeprü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 verä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 im laufenden Betrieb, wenn Sie es irgendwie schaffen, den Dateinamen zu ändern, können Sie das Öffnen der Datei "passwd" oder einer anderen anfordern. Dann kann eine Datei geändert werden und es ist möglich, sich direkt beim Betriebssystem anzumelden.
JVM verwaltet intern den "String Pool". Um die Speichereffizienz zu erreichen, verweist JVM auf das String-Objekt aus dem Pool. Die neuen String-Objekte werden nicht erstellt. Wenn Sie also ein neues Zeichenfolgenliteral erstellen, überprü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 die Sonne, es unveränderlich zu machen.
Dies ist ein guter Punkt für die Wiederverwendung, insbesondere wenn Sie String.intern () verwenden. Es wäre möglich gewesen, es wiederzuverwenden, ohne alle Zeichenfolgen unveränderlich zu machen, aber das Leben wird an diesem Punkt tendenziell kompliziert.
Jsight
3
Keiner dieser Gründe scheint mir heutzutage ein schrecklich triftiger Grund zu sein.
Brian Knoblauch
1
Das Argument der Speichereffizienz überzeugt mich nicht allzu sehr (dh wenn zwei oder mehr String-Objekte dieselben Daten verwenden und eines geändert wird, werden beide geändert). CString-Objekte in MFC umgehen dies mithilfe der Referenzzählung.
RobH
7
Sicherheit ist nicht wirklich Teil der Raison d'être für unveränderliche Zeichenfolgen - Ihr Betriebssystem kopiert Zeichenfolgen in Puffer im Kernelmodus und führt dort eine Zugriffsprüfung durch, um Timing-Angriffe zu vermeiden. Es geht wirklich nur um Thread-Sicherheit und Leistung :)
Snemarch
1
Das Argument der Speichereffizienz funktioniert auch nicht. In einer Muttersprache wie C sind Zeichenfolgenkonstanten einfach Zeiger auf Daten im initialisierten Datenabschnitt - sie sind ohnehin schreibgeschützt / unveränderlich. "Wenn jemand den Wert ändert" - Zeichenfolgen aus dem Pool sind ohnehin schreibgeschützt.
wj32
57
Tatsächlich haben die Gründe, warum Zeichenfolgen in Java unveränderlich sind, nicht viel mit Sicherheit zu tun. Die zwei Hauptgründe sind die folgenden:
Thead Sicherheit:
Strings sind extrem weit verbreitete Objekttypen. Es ist daher mehr oder weniger garantiert, dass es in einer Multithread-Umgebung verwendet wird. Strings sind unveränderlich, um sicherzustellen, dass es sicher ist, Strings zwischen Threads zu teilen. Durch unveränderliche Zeichenfolgen wird sichergestellt, dass Thread B beim Übergeben von Zeichenfolgen von Thread A an einen anderen Thread B die Zeichenfolge von Thread A nicht unerwartet ändern kann.
Dies vereinfacht nicht nur die ohnehin schon ziemlich komplizierte Aufgabe der Multithread-Programmierung, sondern auch die Leistung von Multithread-Anwendungen. Der Zugriff auf veränderbare Objekte muss irgendwie synchronisiert werden, wenn auf sie von mehreren Threads aus 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 Synchronisierung ist für den Programmierer sowohl schwierig als auch zur Laufzeit teuer. Unveränderliche Objekte können nicht geändert werden und müssen daher nicht synchronisiert werden.
Performance:
Während String-Internierung erwähnt wurde, bedeutet dies nur einen geringen Gewinn an Speichereffizienz für Java-Programme. Es werden nur Zeichenfolgenliterale interniert. Dies bedeutet, dass nur die Zeichenfolgen, die in Ihrem Quellcode identisch sind, dasselbe Zeichenfolgenobjekt verwenden . Wenn Ihr Programm dynamisch gleiche 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 übernehmen. 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 char [] von myString gemeinsam nutzt, aber wer weiß, dass es bei Index 0 beginnt und bei Index 5 dieses char [] endet. Um dies in grafische Form zu bringen, würden Sie am Ende Folgendes haben:
| myString |
v v
"The quick brown fox jumps over the lazy dog"<-- shared char[]^^|| myString.substring(0,5)
Dies macht diese Art von Operationen extrem billig und O (1), da die Operation weder von der Länge des ursprünglichen Strings noch von der Länge des zu extrahierenden Teilstrings abhängt. Dieses Verhalten hat auch einige Speichervorteile, da viele Zeichenfolgen ihr zugrunde liegendes Zeichen [] gemeinsam nutzen können.
Das Implementieren von Teilzeichenfolgen als Referenzen, die den zugrunde liegenden Wert teilen, char[]ist eine eher fragwürdige Entwurfsentscheidung. Wenn Sie eine ganze Datei in eine einzelne Zeichenfolge einlesen und nur auf eine 1-stellige Teilzeichenfolge verweisen, muss die gesamte Datei gespeichert werden.
Gabe
5
Genau, ich bin auf dieses spezielle Problem gestoßen, als ich einen Website-Crawler erstellt habe, der nur ein paar Wörter aus der gesamten Seite extrahieren musste. Der HTML-Code der gesamten Seite befand sich im Speicher, und da Teilzeichenfolgen das Zeichen [] gemeinsam nutzen, behielt ich den gesamten HTML-Code bei, obwohl ich nur wenige Bytes benötigte. Eine Problemumgehung besteht darin, einen neuen String (original.substring (.., ..)) zu verwenden. Der String-Konstruktor (String) erstellt eine Kopie des relevanten Bereichs des zugrunde liegenden Arrays.
LordOfThePigs
1
Ein Nachtrag zu späteren Änderungen: Führt seit Jave 7 String.substring()eine vollständige Kopie durch, um die in den obigen Kommentaren genannten Probleme zu vermeiden. In Java 8 werden die beiden Felder, die die char[]Freigabe ermöglichen , nämlich countund offset, entfernt, wodurch der Speicherbedarf von String-Instanzen verringert wird.
Christian Semrau
Ich stimme dem Teil Thead Safety zu, bezweifle jedoch den Fall der Teilzeichenfolge.
Gqqnbig
@LoveRight: Dann überprüfen Sie den Quellcode von java.lang.String ( grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… ). Bis Java 6 (das war es schon) war aktuell, als diese Antwort geschrieben wurde). Ich habe mich anscheinend in Java 7 geändert.
LordOfThePigs
28
Gewindesicherheit und Leistung. Wenn eine Zeichenfolge nicht geändert werden kann, ist es sicher und schnell, eine Referenz zwischen mehreren Threads weiterzugeben. Wenn Zeichenfolgen veränderbar wären, müssten Sie immer alle Bytes der Zeichenfolge in eine neue Instanz kopieren oder eine Synchronisierung bereitstellen. Eine typische Anwendung liest eine Zeichenfolge 100 Mal für jedes Mal, wenn diese Zeichenfolge geändert werden muss. Siehe Wikipedia zur Unveränderlichkeit .
Man sollte sich wirklich fragen: "Warum sollte X veränderlich sein?" Aufgrund der bereits von Princess Fluff bereits erwähnten Vorteile ist es besser, auf Unveränderlichkeit zurückzugreifen . Es sollte eine Ausnahme sein, dass etwas veränderlich ist.
Beeindruckend! Ich kann die Fehlinformationen hier nicht glauben. Strings unveränderlich zu sein, hat nichts mit Sicherheit zu tun. Wenn jemand bereits Zugriff auf die Objekte in einer laufenden Anwendung hat (was angenommen werden muss, wenn Sie versuchen, sich vor jemandem zu schützen, der eine Stringin Ihrer App "hackt" ), bietet er sicherlich viele andere Möglichkeiten zum Hacken.
Es ist eine ziemlich neue Idee, dass die Unveränderlichkeit von StringThreading-Problemen angeht. 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 alle unsere chaotischen Parallelitätsprobleme beheben! Lassen Sie uns alle Objekte unveränderlich machen, und dann können wir das synchronisierte Konstrukt aus der Java-Sprache entfernen.
Der wahre Grund (auf den oben von anderen hingewiesen wurde) ist die Speicheroptimierung. In jeder Anwendung ist es durchaus üblich, dass dasselbe Zeichenfolgenliteral wiederholt verwendet wird. Tatsächlich ist es so üblich, dass viele Compiler vor Jahrzehnten die Optimierung vorgenommen haben, nur eine einzige Instanz eines StringLiteral zu speichern . Der Nachteil dieser Optimierung besteht darin, dass Laufzeitcode, der ein StringLiteral ändert , ein Problem verursacht, da die Instanz für alle anderen Codes geändert wird, die es gemeinsam nutzen. Zum Beispiel wäre es für eine Funktion irgendwo in einer Anwendung nicht gut, das StringLiteral "dog"in zu ändern "cat". A printf("dog")würde dazu führen, "cat"dass in stdout geschrieben wird. Aus diesem Grund musste es eine Möglichkeit geben, sich vor Code zu schützen, der versucht, sich zu ändernStringLiterale (dh machen sie unveränderlich). Einige Compiler (mit Unterstützung des Betriebssystems) würden dies erreichen, indem sie das StringLiteral 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 durchführen. Um das gleiche Problem zu lösen, bei dem diese StringLiterale zur Laufzeit geändert werden, macht Java die StringKlasse einfach unveränderlich (d. H. Sie erhalten keine Setter, mit denen Sie den StringInhalt ändern können ). Strings müsste nicht unveränderlich sein, wenn Stringkeine Internierung von Literalen stattgefunden hätte .
Ich bin absolut nicht einverstanden mit Unveränderlichkeit und Threading-Kommentaren. Mir scheint, Sie verstehen das nicht ganz. Und wenn Josh Bloch, einer der Java-Implementierer, sagt, dass dies eines der Designprobleme war, wie kann das dann eine Fehlinformation sein?
Javashlook
1
Synchronisation ist teuer. Verweise auf veränderbare Objekte müssen synchronisiert werden, nicht für unveränderliche. Dies ist ein Grund, alle Objekte unveränderlich zu machen, es sei denn, sie müssen veränderbar sein. Zeichenfolgen können unveränderlich sein. Dadurch werden sie in mehreren Threads effizienter.
David Thornley
5
@ Jim: Speicheroptimierung ist nicht 'DER' Grund, sondern 'A' Grund. Thread-Sicherheit ist auch ein A-Grund, da unveränderliche Objekte von Natur aus thread-sicher sind und keine teure Synchronisation erfordern, wie David erwähnte. Die Gewindesicherheit ist eigentlich eine Nebenwirkung eines Objekts, das unveränderlich ist. Sie können sich die Synchronisation als eine Möglichkeit vorstellen, das Objekt "vorübergehend" unveränderlich zu machen (ReaderWriterLock macht es schreibgeschützt, und eine reguläre Sperre macht es insgesamt unzugänglich, was es natürlich auch unveränderlich macht).
Triynko
1
@DavidThornley: Durch die Erstellung mehrerer unabhängiger Referenzpfade zu einem veränderlichen Werteinhaber wird dieser effektiv zu einer Entität, und es ist viel schwieriger, auch abgesehen von Threading-Problemen darüber nachzudenken. Im Allgemeinen sind veränderbare Objekte effizienter als unveränderliche Objekte, wenn für jeden genau ein Referenzpfad vorhanden ist. Unveränderliche Objekte ermöglichen jedoch eine effiziente gemeinsame Nutzung des Objektinhalts durch gemeinsame Nutzung von Referenzen. Das beste Muster wird durch Stringund veranschaulicht StringBuffer, aber leider folgen nur wenige andere Typen diesem Modell.
Supercat
7
String ist kein primitiver Typ, aber Sie möchten ihn normalerweise mit Wertesemantik verwenden, dh wie einen Wert.
Ein Wert, dem Sie vertrauen können, ändert sich hinter Ihrem Rücken nicht. Wenn Sie schreiben: String str = someExpr();
Sie möchten nicht, dass es sich ändert, es sei denn, Sie tun etwas damit str.
Stringals ein Objectnatürlich Zeiger Semantik hat, um Wertsemantik zu erhalten und es unveränderlich sein muss.
Ein Faktor ist, dass, wenn Strings veränderlich wäre, Objekte, die Strings speichern , vorsichtig sein müssten, um Kopien zu speichern, damit sich ihre internen Daten nicht ohne vorherige Ankündigung ändern. Angesichts der Tatsache, dass Strings ein ziemlich primitiver Typ wie Zahlen ist, ist es schön, wenn man sie so behandeln kann, als ob sie als Wert übergeben würden, selbst wenn sie als Referenz übergeben werden (was auch hilft, Speicherplatz zu sparen).
Fazit: Sie befinden sich in einem unveränderlichen Zustand, der dem Compiler bekannt ist. Natürlich gilt das oben Gesagte nur für .NET-Zeichenfolgen, da Java keine Zeiger hat. Eine Zeichenfolge kann jedoch mithilfe von Zeigern in C # vollständig veränderbar sein. Es ist nicht so, wie Zeiger verwendet werden sollen, praktisch verwendet werden oder sicher verwendet werden. es ist jedoch möglich, die gesamte "veränderbare" Regel zu verbiegen. Normalerweise können Sie einen Index einer Zeichenfolge nicht direkt ändern. Dies ist der einzige Weg. Es gibt eine Möglichkeit, dies zu verhindern, indem Zeigerinstanzen von Zeichenfolgen nicht zugelassen werden oder eine Kopie erstellt wird, wenn auf eine Zeichenfolge verwiesen wird. Dies ist jedoch auch nicht der Fall, wodurch Zeichenfolgen in C # nicht vollständig unveränderlich werden.
+1. .NET-Zeichenfolgen sind nicht wirklich unveränderlich. Tatsächlich wird dies in den Klassen String und StringBuilder aus Perf-Gründen immer durchgeführt.
James Ko
3
Für die meisten Zwecke ist eine "Zeichenfolge" (verwendet / behandelt als / gedacht / angenommen) eine bedeutungsvolle atomare Einheit, genau wie eine Zahl .
Die Frage, warum die einzelnen Zeichen einer Zeichenfolge nicht veränderbar sind, entspricht der Frage, warum die einzelnen Bits einer Ganzzahl nicht veränderbar sind.
Sie sollten wissen warum. Denken Sie nur darüber nach.
Ich hasse es, es zu sagen, aber leider diskutieren wir darüber, weil unsere Sprache scheiße ist, und wir versuchen, ein einzelnes Wort, eine Zeichenfolge , zu verwenden, um ein komplexes, kontextbezogenes Konzept oder eine Klasse von Objekten zu beschreiben.
Wir führen Berechnungen und Vergleiche mit "Strings" durch, ähnlich wie wir es mit Zahlen tun. Wenn Zeichenfolgen (oder Ganzzahlen) veränderbar wären, müssten wir speziellen Code schreiben, um ihre Werte in unveränderliche lokale Formen zu sperren, damit jede Art von Berechnung zuverlässig durchgeführt werden kann. Daher ist es am besten, sich eine Zeichenfolge wie eine numerische Kennung vorzustellen, aber anstatt 16, 32 oder 64 Bit lang zu sein, kann sie Hunderte von Bit lang sein.
Wenn jemand "String" sagt, denken wir alle an verschiedene Dinge. Diejenigen, die es einfach als eine Reihe von Charakteren ohne besonderen Zweck betrachten, werden natürlich entsetzt sein, dass jemand gerade entschieden hat, dass sie diese Charaktere nicht manipulieren können sollten. Die "string" -Klasse besteht jedoch nicht nur aus einer Reihe 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 Leute über das "Manipulieren von Strings" sprechen, sprechen sie vielleicht wirklich über das Manipulieren von Zeichen , um Strings zu erstellen , und ein StringBuilder ist dafür großartig.
Überlegen Sie sich für einen Moment, wie es wäre, wenn Zeichenfolgen 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 Benutzernamenzeichenfolge absichtlich oder unbeabsichtigt von einem anderen Thread geändert wird, während diese Funktion sie verwendet:
Bei Sicherheit geht es nicht nur um "Zugangskontrolle", sondern auch um "Sicherheit" und "Gewährleistung der Korrektheit". Wenn eine Methode nicht einfach geschrieben und abhängig gemacht werden kann, um eine einfache Berechnung oder einen Vergleich zuverlässig durchzuführen, ist es nicht sicher, sie aufzurufen, aber es wäre sicher, die Programmiersprache selbst in Frage zu stellen.
In C # kann eine Zeichenfolge durch ihren Zeiger (Verwendung unsafe) oder einfach durch Reflexion (Sie können das zugrunde liegende Feld leicht erhalten) geändert werden. Dies macht den Punkt zur Sicherheit ungültig, da jeder, der absichtlich eine Zeichenfolge ändern möchte, dies ganz einfach tun kann. Es bietet Programmierern jedoch Sicherheit: Wenn Sie nichts Besonderes tun, ist die Zeichenfolge garantiert unveränderlich (aber nicht threadsicher!).
Abel
Ja, Sie können die Bytes eines Datenobjekts (Zeichenfolge, Int usw.) über Zeiger ändern. Wir sprechen jedoch darüber, warum die Zeichenfolgenklasse in dem Sinne unveränderlich ist, dass keine öffentlichen Methoden zum Ändern ihrer Zeichen integriert sind. Ich habe gesagt, dass eine Zeichenfolge einer Zahl sehr ähnlich ist, da die Manipulation einzelner Zeichen nicht sinnvoller ist als die Manipulation einzelner Bits einer Zahl (wenn Sie eine Zeichenfolge als ganzes Token (nicht als Byte-Array) und eine Zahl als behandeln Ein numerischer Wert (nicht als Bitfeld). Wir sprechen auf konzeptioneller Objektebene, nicht auf
Unterobjektebene
2
Zur Verdeutlichung sind Zeiger in objektorientiertem Code von Natur aus unsicher, gerade weil sie die für eine Klasse definierten öffentlichen Schnittstellen umgehen. Was ich sagte, war, dass eine Funktion leicht ausgetrickst werden kann, wenn die öffentliche Schnittstelle für eine Zeichenfolge es erlaubt, sie durch andere Threads zu ändern. Natürlich kann es immer ausgetrickst werden, indem direkt mit Zeigern auf Daten zugegriffen wird, aber nicht so einfach oder unbeabsichtigt.
Triynko
1
'Zeiger in objektorientiertem Code sind von Natur aus unsicher', es sei denn, Sie nennen sie Referenzen . Verweise in Java unterscheiden sich nicht von Zeigern in C ++ (nur die Zeigerarithmetik ist deaktiviert). Ein anderes Konzept ist die Speicherverwaltung, die verwaltet oder manuell durchgeführt werden kann, aber das ist etwas anderes. Sie könnten Referenzsemantik (Zeiger ohne Arithmetik) ohne GC haben (das Gegenteil wäre schwieriger in dem Sinne, dass die Semantik der Erreichbarkeit schwieriger sauber zu machen wäre, aber nicht nicht durchführbar)
David Rodríguez - Dribeas
Die andere Sache ist, dass wenn Zeichenfolgen fast unveränderlich sind, aber nicht ganz so (ich weiß hier nicht genug CLI), dies aus Sicherheitsgründen wirklich schlecht sein kann. In einigen älteren Java-Implementierungen konnten Sie dies tun, und ich fand einen Codeausschnitt, der diesen zum Internalisieren von Zeichenfolgen verwendete (versuchen Sie, andere interne Zeichenfolgen mit demselben Wert zu suchen, den Zeiger gemeinsam zu nutzen, den alten Speicherblock zu entfernen) und verwendete die Hintertür um den Inhalt der Zeichenfolge neu zu schreiben und ein falsches Verhalten in einer anderen Klasse zu erzwingen. (
Erwägen
3
Unveränderlichkeit ist nicht so eng mit Sicherheit verbunden. Dafür erhalten Sie zumindest in .NET die SecureStringKlasse.
Später bearbeiten: In Java finden Sie GuardedStringeine ähnliche Implementierung.
Die Entscheidung, String in C ++ veränderbar zu machen, verursacht viele Probleme. Siehe diesen ausgezeichneten Artikel von Kelvin Henney über Mad COW Disease .
Es ist ein Kompromiss. Strings gehen in den StringPool und wenn Sie mehrere identische Strings erstellen , teilen sie sich den gleichen Speicher. Die Designer gingen davon aus, dass diese speichersparende Technik für den allgemeinen Fall gut geeignet ist, da Programme häufig über dieselben Zeichenfolgen laufen.
Der Nachteil ist, dass Verkettungen viele zusätzliche Strings verursachen, die nur vorübergehend sind und nur zu Müll werden, was die Speicherleistung tatsächlich beeinträchtigt. Sie müssen StringBufferund StringBuilder(in Java StringBuilderauch in .NET) verwenden, um in diesen Fällen Speicher zu erhalten.
Beachten Sie, dass der "Zeichenfolgenpool" nicht automatisch für ALLE Zeichenfolgen verwendet wird, es sei denn, Sie verwenden explizit "inter ()" - Zeichenfolgen.
Jsight
2
Strings in Java sind nicht wirklich unveränderlich. Sie können ihre Werte mithilfe von Reflection und / oder Klassenladen ändern. Sie sollten aus Sicherheitsgründen nicht von dieser Eigenschaft abhängig sein. Beispiele finden Sie unter: Zaubertrick in Java
Ich glaube, dass Sie solche Tricks nur ausführen können, wenn Ihr Code mit vollem Vertrauen ausgeführt wird, daher gibt es keinen Sicherheitsverlust. Sie können auch JNI verwenden, um direkt auf den Speicherort zu schreiben, an dem die Zeichenfolgen gespeichert sind.
Antoine Aubry
Eigentlich glaube ich, dass man jedes unveränderliche Objekt durch Reflexion verändern kann.
Gqqnbig
0
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 Verwirrung darüber, welche Änderungen welche Referenzen beeinflussen. Ebenso wie Integer unveränderlich sein muss, um sich wie int zu verhalten, müssen sich Strings unveränderlich verhalten, um sich wie Primitive zu verhalten. In C ++ erfolgt die Übergabe von Zeichenfolgen nach Wert ohne explizite Erwähnung im Quellcode.
using System;
using System.Runtime.InteropServices;
namespace Guess{classProgram{staticvoidMain(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();}}}}
Es ist hauptsächlich aus Sicherheitsgründen. Es ist viel schwieriger, ein System zu sichern, wenn Sie nicht darauf vertrauen können, dass Ihre StringSysteme manipulationssicher sind.
String
ist tatsächlich intern veränderbar.StringBuilder
In .NET 2.0 mutiert eine Zeichenfolge . Ich lasse es einfach hier.Antworten:
Laut Effective Java , Kapitel 4, Seite 73, 2. Ausgabe:
Weitere kleine Punkte aus demselben Kapitel:
quelle
report2.Text = report1.Text;
. Dann, woanders, den Text ändern :report2.Text.Replace(someWord, someOtherWord);
. Dies würde sowohl den ersten als auch den zweiten Bericht ändern.Es gibt mindestens zwei Gründe.
Erstens - Sicherheit http://www.javafaq.nu/java-article1060.html
Zweitens - Speichereffizienz http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html
quelle
Tatsächlich haben die Gründe, warum Zeichenfolgen in Java unveränderlich sind, nicht viel mit Sicherheit zu tun. Die zwei Hauptgründe sind die folgenden:
Thead Sicherheit:
Strings sind extrem weit verbreitete Objekttypen. Es ist daher mehr oder weniger garantiert, dass es in einer Multithread-Umgebung verwendet wird. Strings sind unveränderlich, um sicherzustellen, dass es sicher ist, Strings zwischen Threads zu teilen. Durch unveränderliche Zeichenfolgen wird sichergestellt, dass Thread B beim Übergeben von Zeichenfolgen von Thread A an einen anderen Thread B die Zeichenfolge von Thread A nicht unerwartet ändern kann.
Dies vereinfacht nicht nur die ohnehin schon ziemlich komplizierte Aufgabe der Multithread-Programmierung, sondern auch die Leistung von Multithread-Anwendungen. Der Zugriff auf veränderbare Objekte muss irgendwie synchronisiert werden, wenn auf sie von mehreren Threads aus 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 Synchronisierung ist für den Programmierer sowohl schwierig als auch zur Laufzeit teuer. Unveränderliche Objekte können nicht geändert werden und müssen daher nicht synchronisiert werden.
Performance:
Während String-Internierung erwähnt wurde, bedeutet dies nur einen geringen Gewinn an Speichereffizienz für Java-Programme. Es werden nur Zeichenfolgenliterale interniert. Dies bedeutet, dass nur die Zeichenfolgen, die in Ihrem Quellcode identisch sind, dasselbe Zeichenfolgenobjekt verwenden . Wenn Ihr Programm dynamisch gleiche 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 übernehmen. 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 char [] von myString gemeinsam nutzt, aber wer weiß, dass es bei Index 0 beginnt und bei Index 5 dieses char [] endet. Um dies in grafische Form zu bringen, würden Sie am Ende Folgendes haben:
Dies macht diese Art von Operationen extrem billig und O (1), da die Operation weder von der Länge des ursprünglichen Strings noch von der Länge des zu extrahierenden Teilstrings abhängt. Dieses Verhalten hat auch einige Speichervorteile, da viele Zeichenfolgen ihr zugrunde liegendes Zeichen [] gemeinsam nutzen können.
quelle
char[]
ist eine eher fragwürdige Entwurfsentscheidung. Wenn Sie eine ganze Datei in eine einzelne Zeichenfolge einlesen und nur auf eine 1-stellige Teilzeichenfolge verweisen, muss die gesamte Datei gespeichert werden.String.substring()
eine vollständige Kopie durch, um die in den obigen Kommentaren genannten Probleme zu vermeiden. In Java 8 werden die beiden Felder, die diechar[]
Freigabe ermöglichen , nämlichcount
undoffset
, entfernt, wodurch der Speicherbedarf von String-Instanzen verringert wird.Gewindesicherheit und Leistung. Wenn eine Zeichenfolge nicht geändert werden kann, ist es sicher und schnell, eine Referenz zwischen mehreren Threads weiterzugeben. Wenn Zeichenfolgen veränderbar wären, müssten Sie immer alle Bytes der Zeichenfolge in eine neue Instanz kopieren oder eine Synchronisierung bereitstellen. Eine typische Anwendung liest eine Zeichenfolge 100 Mal für jedes Mal, wenn diese Zeichenfolge geändert werden muss. Siehe Wikipedia zur Unveränderlichkeit .
quelle
Man sollte sich wirklich fragen: "Warum sollte X veränderlich sein?" Aufgrund der bereits von Princess Fluff bereits erwähnten Vorteile ist es besser, auf Unveränderlichkeit zurückzugreifen . Es sollte eine Ausnahme sein, dass etwas veränderlich ist.
Leider sind die meisten aktuellen Programmiersprachen standardmäßig veränderlich, aber hoffentlich liegt der Standard in Zukunft eher in der Unveränderlichkeit (siehe Eine Wunschliste für die nächste Mainstream-Programmiersprache ).
quelle
Beeindruckend! Ich kann die Fehlinformationen hier nicht glauben.
String
s unveränderlich zu sein, hat nichts mit Sicherheit zu tun. Wenn jemand bereits Zugriff auf die Objekte in einer laufenden Anwendung hat (was angenommen werden muss, wenn Sie versuchen, sich vor jemandem zu schützen, der eineString
in Ihrer App "hackt" ), bietet er sicherlich viele andere Möglichkeiten zum Hacken.Es ist eine ziemlich neue Idee, dass die Unveränderlichkeit von
String
Threading-Problemen angeht. 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 alle unsere chaotischen Parallelitätsprobleme beheben! Lassen Sie uns alle Objekte unveränderlich machen, und dann können wir das synchronisierte Konstrukt aus der Java-Sprache entfernen.Der wahre Grund (auf den oben von anderen hingewiesen wurde) ist die Speicheroptimierung. In jeder Anwendung ist es durchaus üblich, dass dasselbe Zeichenfolgenliteral wiederholt verwendet wird. Tatsächlich ist es so üblich, dass viele Compiler vor Jahrzehnten die Optimierung vorgenommen haben, nur eine einzige Instanz eines
String
Literal zu speichern . Der Nachteil dieser Optimierung besteht darin, dass Laufzeitcode, der einString
Literal ändert , ein Problem verursacht, da die Instanz für alle anderen Codes geändert wird, die es gemeinsam nutzen. Zum Beispiel wäre es für eine Funktion irgendwo in einer Anwendung nicht gut, dasString
Literal"dog"
in zu ändern"cat"
. Aprintf("dog")
würde dazu führen,"cat"
dass in stdout geschrieben wird. Aus diesem Grund musste es eine Möglichkeit geben, sich vor Code zu schützen, der versucht, sich zu ändernString
Literale (dh machen sie unveränderlich). Einige Compiler (mit Unterstützung des Betriebssystems) würden dies erreichen, indem sie dasString
Literal 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 durchführen. Um das gleiche Problem zu lösen, bei dem diese
String
Literale zur Laufzeit geändert werden, macht Java dieString
Klasse einfach unveränderlich (d. H. Sie erhalten keine Setter, mit denen Sie denString
Inhalt ändern können ).String
s müsste nicht unveränderlich sein, wennString
keine Internierung von Literalen stattgefunden hätte .quelle
String
und veranschaulichtStringBuffer
, aber leider folgen nur wenige andere Typen diesem Modell.String
ist kein primitiver Typ, aber Sie möchten ihn normalerweise mit Wertesemantik verwenden, dh wie einen Wert.Ein Wert, dem Sie vertrauen können, ändert sich hinter Ihrem Rücken nicht. Wenn Sie schreiben:
String str = someExpr();
Sie möchten nicht, dass es sich ändert, es sei denn, Sie tun etwas damitstr
.String
als einObject
natürlich Zeiger Semantik hat, um Wertsemantik zu erhalten und es unveränderlich sein muss.quelle
Ein Faktor ist, dass, wenn
String
s veränderlich wäre, Objekte, dieString
s speichern , vorsichtig sein müssten, um Kopien zu speichern, damit sich ihre internen Daten nicht ohne vorherige Ankündigung ändern. Angesichts der Tatsache, dassString
s ein ziemlich primitiver Typ wie Zahlen ist, ist es schön, wenn man sie so behandeln kann, als ob sie als Wert übergeben würden, selbst wenn sie als Referenz übergeben werden (was auch hilft, Speicherplatz zu sparen).quelle
Ich weiß, das ist eine Beule, aber ... Sind sie wirklich unveränderlich? Folgendes berücksichtigen.
...
Sie könnten es sogar zu einer Erweiterungsmethode machen.
Welches macht die folgende Arbeit
Fazit: Sie befinden sich in einem unveränderlichen Zustand, der dem Compiler bekannt ist. Natürlich gilt das oben Gesagte nur für .NET-Zeichenfolgen, da Java keine Zeiger hat. Eine Zeichenfolge kann jedoch mithilfe von Zeigern in C # vollständig veränderbar sein. Es ist nicht so, wie Zeiger verwendet werden sollen, praktisch verwendet werden oder sicher verwendet werden. es ist jedoch möglich, die gesamte "veränderbare" Regel zu verbiegen. Normalerweise können Sie einen Index einer Zeichenfolge nicht direkt ändern. Dies ist der einzige Weg. Es gibt eine Möglichkeit, dies zu verhindern, indem Zeigerinstanzen von Zeichenfolgen nicht zugelassen werden oder eine Kopie erstellt wird, wenn auf eine Zeichenfolge verwiesen wird. Dies ist jedoch auch nicht der Fall, wodurch Zeichenfolgen in C # nicht vollständig unveränderlich werden.
quelle
Für die meisten Zwecke ist eine "Zeichenfolge" (verwendet / behandelt als / gedacht / angenommen) eine bedeutungsvolle atomare Einheit, genau wie eine Zahl .
Die Frage, warum die einzelnen Zeichen einer Zeichenfolge nicht veränderbar sind, entspricht der Frage, warum die einzelnen Bits einer Ganzzahl nicht veränderbar sind.
Sie sollten wissen warum. Denken Sie nur darüber nach.
Ich hasse es, es zu sagen, aber leider diskutieren wir darüber, weil unsere Sprache scheiße ist, und wir versuchen, ein einzelnes Wort, eine Zeichenfolge , zu verwenden, um ein komplexes, kontextbezogenes Konzept oder eine Klasse von Objekten zu beschreiben.
Wir führen Berechnungen und Vergleiche mit "Strings" durch, ähnlich wie wir es mit Zahlen tun. Wenn Zeichenfolgen (oder Ganzzahlen) veränderbar wären, müssten wir speziellen Code schreiben, um ihre Werte in unveränderliche lokale Formen zu sperren, damit jede Art von Berechnung zuverlässig durchgeführt werden kann. Daher ist es am besten, sich eine Zeichenfolge wie eine numerische Kennung vorzustellen, aber anstatt 16, 32 oder 64 Bit lang zu sein, kann sie Hunderte von Bit lang sein.
Wenn jemand "String" sagt, denken wir alle an verschiedene Dinge. Diejenigen, die es einfach als eine Reihe von Charakteren ohne besonderen Zweck betrachten, werden natürlich entsetzt sein, dass jemand gerade entschieden hat, dass sie diese Charaktere nicht manipulieren können sollten. Die "string" -Klasse besteht jedoch nicht nur aus einer Reihe von Zeichen. Es ist ein
STRING
, keinchar[]
. 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 Leute über das "Manipulieren von Strings" sprechen, sprechen sie vielleicht wirklich über das Manipulieren von Zeichen , um Strings zu erstellen , und ein StringBuilder ist dafür großartig.Überlegen Sie sich für einen Moment, wie es wäre, wenn Zeichenfolgen 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 Benutzernamenzeichenfolge absichtlich oder unbeabsichtigt von einem anderen Thread geändert wird, während diese Funktion sie verwendet:
Bei Sicherheit geht es nicht nur um "Zugangskontrolle", sondern auch um "Sicherheit" und "Gewährleistung der Korrektheit". Wenn eine Methode nicht einfach geschrieben und abhängig gemacht werden kann, um eine einfache Berechnung oder einen Vergleich zuverlässig durchzuführen, ist es nicht sicher, sie aufzurufen, aber es wäre sicher, die Programmiersprache selbst in Frage zu stellen.
quelle
unsafe
) oder einfach durch Reflexion (Sie können das zugrunde liegende Feld leicht erhalten) geändert werden. Dies macht den Punkt zur Sicherheit ungültig, da jeder, der absichtlich eine Zeichenfolge ändern möchte, dies ganz einfach tun kann. Es bietet Programmierern jedoch Sicherheit: Wenn Sie nichts Besonderes tun, ist die Zeichenfolge garantiert unveränderlich (aber nicht threadsicher!).Unveränderlichkeit ist nicht so eng mit Sicherheit verbunden. Dafür erhalten Sie zumindest in .NET die
SecureString
Klasse.Später bearbeiten: In Java finden Sie
GuardedString
eine ähnliche Implementierung.quelle
Die Entscheidung, String in C ++ veränderbar zu machen, verursacht viele Probleme. Siehe diesen ausgezeichneten Artikel von Kelvin Henney über Mad COW Disease .
COW = Beim Schreiben kopieren.
quelle
Es ist ein Kompromiss.
String
s gehen in denString
Pool und wenn Sie mehrere identischeString
s erstellen , teilen sie sich den gleichen Speicher. Die Designer gingen davon aus, dass diese speichersparende Technik für den allgemeinen Fall gut geeignet ist, da Programme häufig über dieselben Zeichenfolgen laufen.Der Nachteil ist, dass Verkettungen viele zusätzliche
String
s verursachen, die nur vorübergehend sind und nur zu Müll werden, was die Speicherleistung tatsächlich beeinträchtigt. Sie müssenStringBuffer
undStringBuilder
(in JavaStringBuilder
auch in .NET) verwenden, um in diesen Fällen Speicher zu erhalten.quelle
String
s in Java sind nicht wirklich unveränderlich. Sie können ihre Werte mithilfe von Reflection und / oder Klassenladen ändern. Sie sollten aus Sicherheitsgründen nicht von dieser Eigenschaft abhängig sein. Beispiele finden Sie unter: Zaubertrick in Javaquelle
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 Verwirrung darüber, welche Änderungen welche Referenzen beeinflussen. Ebenso wie Integer unveränderlich sein muss, um sich wie int zu verhalten, müssen sich Strings unveränderlich verhalten, um sich wie Primitive zu verhalten. In C ++ erfolgt die Übergabe von Zeichenfolgen nach Wert ohne explizite Erwähnung im Quellcode.
quelle
Für fast jede Regel gibt es eine Ausnahme:
quelle
Es ist hauptsächlich aus Sicherheitsgründen. Es ist viel schwieriger, ein System zu sichern, wenn Sie nicht darauf vertrauen können, dass Ihre
String
Systeme manipulationssicher sind.quelle