415 Stimmen

Ist ein Java-String wirklich unveränderlich?

Wir alle wissen, dass String in Java unveränderlich ist, aber schau dir den folgenden Code an:

String s1 = "Hello World";  
String s2 = "Hello World";  
String s3 = s1.substring(6);  
System.out.println(s1); // Hello World  
System.out.println(s2); // Hello World  
System.out.println(s3); // World  

Field field = String.class.getDeclaredField("value");  
field.setAccessible(true);  
char[] value = (char[])field.get(s1);  
value[6] = 'J';  
value[7] = 'a';  
value[8] = 'v';  
value[9] = 'a';  
value[10] = '!';  

System.out.println(s1); // Hello Java!  
System.out.println(s2); // Hello Java!  
System.out.println(s3); // World  

Warum funktioniert dieses Programm so? Und warum wird der Wert von s1 und s2 geändert, aber nicht von s3?

414voto

Harald K Punkte 25010

String ist unveränderlich, aber das bedeutet nur, dass Sie es nicht über seine öffentliche API ändern können.

Was Sie hier tun, ist, die normale API zu umgehen, indem Sie Reflexion verwenden. Auf die gleiche Weise können Sie die Werte von Aufzählungstypen ändern, die Suchtabelle, die bei der Integer-Autoboxing verwendet wird, ändern, usw.

Der Grund, warum s1 und s2 ihren Wert ändern, liegt darin, dass sie sich beide auf denselben internierten String beziehen. Der Compiler macht das (wie in anderen Antworten erwähnt).

Der Grund, warum s3 seinen Wert tatsächlich nicht ändert, war für mich etwas überraschend, da ich dachte, dass es das value-Array teilen würde (das tat es in früheren Versionen von Java, vor Java 7u6). Wenn wir uns jedoch den Quellcode von String ansehen, können wir sehen, dass das value-Zeichenarray für einen Teilzeichenfolge tatsächlich kopiert wird (unter Verwendung von Arrays.copyOfRange(..)). Deshalb bleibt es unverändert.

Sie können einen SecurityManager installieren, um zu verhindern, dass bösartiger Code solche Dinge tut. Beachten Sie jedoch, dass einige Bibliotheken darauf angewiesen sind, diese Art von Reflexionstricks zu verwenden (typischerweise ORM-Tools, AOP-Bibliotheken, etc).

*) Ich habe ursprünglich geschrieben, dass Strings nicht wirklich unveränderlich sind, sondern nur "effektiv unveränderlich". Dies könnte in der aktuellen Implementierung von String irreführend sein, da das value-Array tatsächlich als private final markiert ist. Es ist jedoch erwähnenswert, dass es in Java keine Möglichkeit gibt, ein Array als unveränderlich zu deklarieren, daher muss darauf geachtet werden, es nicht außerhalb seiner Klasse freizulegen, selbst mit den richtigen Zugriffsmodifikatoren.


Da dieses Thema anscheinend überwältigend beliebt ist, hier einige empfohlene weitere Lektüre: Heinz Kabutz's Reflection Madness Vortrag von der JavaZone 2009, der viele der Probleme im OP behandelt, zusammen mit anderem Reflexions... nun ja... Wahnsinn.

Es wird erklärt, warum dies manchmal nützlich ist. Und warum Sie es die meiste Zeit vermeiden sollten. :-)

96voto

Zaheer Ahmed Punkte 27340

Wenn in Java zwei String-Primitivvariablen mit demselben Literal initialisiert werden, wird beiden Variablen dieselbe Referenz zugewiesen:

String Test1="Hello World";
String Test2="Hello World";
System.out.println(test1==test2); // true

initialization

Deshalb gibt der Vergleich true zurück. Der dritte String wird mit substring() erstellt, was einen neuen String erstellt, anstatt auf denselben zu verweisen.

sub string

Wenn Sie auf einen String mit Reflektion zugreifen, erhalten Sie den tatsächlichen Zeiger:

Field field = String.class.getDeclaredField("value");
field.setAccessible(true);

Das Ändern dieses Zeigers ändert den String, der ihn hält. Da aber s3 mit einem neuen String aufgrund von substring() erstellt wurde, würde sich dieser nicht ändern.

change

51voto

Bohemian Punkte 386825

Sie verwenden Reflexion, um die Unveränderlichkeit von String zu umgehen - es handelt sich um eine Form von "Angriff".

Es gibt viele Beispiele, die Sie erstellen können (z. B. Sie können sogar ein Void-Objekt instanziieren too), aber das bedeutet nicht, dass String nicht "unveränderlich" ist.

Es gibt Anwendungsfälle, in denen dieser Code zu Ihrem Vorteil verwendet werden kann und "guter Code" ist, z. B. das Löschen von Passwörtern aus dem Speicher zum frühestmöglichen Zeitpunkt (vor dem GC).

Je nach Sicherheitsmanager können Sie Ihren Code möglicherweise nicht ausführen.

30voto

Ankur Punkte 32819

Sie verwenden Reflektion, um auf die "Implementierungsdetails" des Zeichenobjekts zuzugreifen. Unveränderlichkeit ist das Merkmal der öffentlichen Schnittstelle eines Objekts.

24voto

Hauke Ingmar Schmidt Punkte 11420

Sichtbarkeitsmodifikatoren und final (d.h. Unveränderlichkeit) sind keine Maßnahme gegen bösartigen Code in Java; sie sind lediglich Werkzeuge zum Schutz vor Fehlern und zur Verbesserung der Wartbarkeit des Codes (einer der Hauptvorteile des Systems). Deshalb können Sie interne Implementierungsdetails wie das zugrunde liegende Zeichenarray für Strings über Reflexion zugreifen.

Der zweite Effekt, den Sie sehen, ist, dass sich alle Strings ändern, während es aussieht, als würden Sie nur s1 ändern. Es ist eine bestimmte Eigenschaft von Java String-Literale, dass sie automatisch interniert, d.h. zwischengespeichert werden. Zwei String-Literale mit dem gleichen Wert sind tatsächlich dasselbe Objekt. Wenn Sie einen String mit new erstellen, wird er nicht automatisch interniert und Sie werden diesen Effekt nicht sehen.

#substring hat bis vor kurzem (Java 7u6) auf ähnliche Weise funktioniert, was das Verhalten in der ursprünglichen Version Ihrer Frage erklärt hätte. Es hat kein neues zugrunde liegendes Zeichenarray erstellt, sondern das vom ursprünglichen String wiederverwendet; es hat einfach ein neues String-Objekt erstellt, das einen Offset und eine Länge verwendet, um nur einen Teil dieses Arrays darzustellen. Dies funktionierte im Allgemeinen, da Strings unveränderlich sind - es sei denn, Sie umgehen das. Diese Eigenschaft von #substring bedeutete auch, dass der gesamte ursprüngliche String nicht vom Garbage Collector freigegeben werden konnte, wenn ein kürzerer Teilstring noch existierte.

In der aktuellen Java-Version und Ihrer aktuellen Version der Frage gibt es kein seltsames Verhalten von #substring.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X