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?

11voto

Krease Punkte 15196

Die String-Unveränderlichkeit erfolgt aus der Interface-Perspektive. Sie verwenden Reflexion, um das Interface zu umgehen und die internen Teile der String-Instanzen direkt zu ändern.

s1 und s2 werden beide geändert, da sie beide derselben "intern"-String-Instanz zugewiesen sind. Näheres zu diesem Thema erfahren Sie in diesem Artikel über String-Gleichheit und Interning. Es könnte Sie überraschen zu erfahren, dass in Ihrem Beispielcode s1 == s2 true zurückgibt!

10voto

manikanta Punkte 7272

Welche Version von Java verwenden Sie? Ab Java 1.7.0_06 hat Oracle die interne Darstellung von Strings geändert, insbesondere von Teilen.

Zitierend aus Oracle Tunes Java's Internal String Representation:

Im neuen Paradigma wurden die String-Offset- und Count-Felder entfernt, sodass Teilstrings nicht mehr den zugrunde liegenden char[]-Wert gemeinsam nutzen.

Mit dieser Änderung kann dies ohne Reflektion geschehen (???).

7voto

Scott Wisniewski Punkte 23882

Es gibt wirklich zwei Fragen hier:

  1. Sind Zeichenfolgen wirklich unveränderlich?
  2. Warum wird s3 nicht geändert?

Zu Punkt 1: Mit Ausnahme von ROM gibt es keinen unveränderlichen Speicher in Ihrem Computer. Heutzutage ist sogar ROM manchmal beschreibbar. Irgendwo gibt es immer einen Code (ob es sich um den Kernel oder nativen Code handelt, der Ihre verwaltete Umgebung umgeht), der an Ihre Speicheradresse schreiben kann. Also sind sie in "Wirklichkeit" nicht absolut unveränderlich.

Zu Punkt 2: Dies liegt wahrscheinlich daran, dass das Teilzeichenfolge wahrscheinlich eine neue Zeichenfolgeninstanz zuweist, die wahrscheinlich das Array kopiert. Es ist möglich, das Teilzeichenfolge auf eine Weise implementiert ist, dass es keine Kopie erstellt, aber das bedeutet nicht, dass es so ist. Es gibt Kompromisse.

Zum Beispiel, sollte das Halten eines Verweises auf wirklichGrosseZeichenkette.teilzeichenkette(wirklichGrosseZeichenkette.länge - 2) eine große Menge an Speicher am Leben erhalten oder nur wenige Bytes?

Das hängt davon ab, wie die Teilzeichenfolge implementiert ist. Eine tiefe Kopie hält weniger Speicher am Leben, läuft aber etwas langsamer. Eine flache Kopie hält mehr Speicher am Leben, ist aber schneller. Die Verwendung einer tiefen Kopie kann auch die Fragmentierung des Heaps reduzieren, da das Zeichenfolgenobjekt und sein Puffer in einem Block zugewiesen werden können, im Gegensatz zu 2 separaten Heap-Zuweisungen.

In jedem Fall scheint Ihr JVM sich entschieden zu haben, tiefe Kopien für Teilzeichenfolgenaufrufe zu verwenden.

5voto

AbhijeetMishra Punkte 61

Gemäß dem Konzept des Pooling zeigen alle String-Variablen, die den gleichen Wert enthalten, auf dieselbe Speicheradresse. Daher zeigen s1 und s2, die beide den gleichen Wert von "Hello World" enthalten, auf dieselbe Speicheradresse (sagen wir M1).

Andererseits enthält s3 "World", daher wird es auf eine andere Speicherzuweisung zeigen (sagen wir M2).

Was jetzt passiert, ist, dass der Wert von S1 geändert wird (indem der char [] Wert verwendet wird). Daher wurde der Wert an der Speicheradresse M1, auf die sowohl s1 als auch s2 zeigen, geändert.

Als Ergebnis wurde die Speicheradresse M1 modifiziert, was zur Änderung des Werts von s1 und s2 führt.

Aber der Wert von M2 bleibt unverändert, daher enthält s3 weiterhin den gleichen ursprünglichen Wert.

5voto

Der Grund, warum sich s3 tatsächlich nicht ändert, liegt darin, dass in Java, wenn Sie ein Teilzeichenfolge durchführen, der Wert des Zeichenarrays für eine Teilzeichenfolge intern kopiert wird (unter Verwendung von Arrays.copyOfRange()).

S1 und s2 sind gleich, weil sie in Java beide auf dieselbe internierte Zeichenfolge verweisen. Es ist in Java so vorgesehen.

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