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?

5voto

Andrey Chaschev Punkte 15685

Um zu @haraldK's Antwort hinzuzufügen - dies ist ein Sicherheits-Hack, der zu schwerwiegenden Auswirkungen in der App führen könnte.

Das Erste ist eine Änderung an einem konstanten String, der im String-Pool gespeichert ist. Wenn ein String deklariert wird als String s = "Hello World";, wird er in einen speziellen Objektpool für mögliche spätere Verwendung platziert. Das Problem ist, dass der Compiler zur Kompilierzeit eine Referenz zur modifizierten Version platziert und sobald der Benutzer zur Laufzeit den im Pool gespeicherten String modifiziert, zeigen alle Referenzen im Code auf die modifizierte Version. Dies würde zu einem folgenden Fehler führen:

System.out.println("Hello World"); 

Wird ausgeben:

Hello Java!

Es gab ein weiteres Problem, das ich erlebt habe, als ich eine auf solche riskanten Strings basierende aufwändige Berechnung implementiert habe. Es gab einen Fehler, der ungefähr 1 von 1000000 Mal bei der Berechnung aufgetreten ist, was das Ergebnis undeterministisch machte. Ich konnte das Problem lösen, indem ich den JIT ausschaltete - ich habe immer das gleiche Ergebnis erhalten, wenn der JIT ausgeschaltet war. Meine Vermutung ist, dass der Grund dafür dieser String-Sicherheits-Hack war, der einige der JIT-Optimierungsverträge gebrochen hat.

2voto

SpacePrez Punkte 1076

Saite ist unveränderlich, aber durch Reflexion ist es möglich, die String-Klasse zu ändern. Sie haben gerade die String-Klasse in Echtzeit neu definiert. Sie könnten Methoden neu definieren, um öffentlich oder privat oder statisch zu sein, wenn Sie wollten.

2voto

Zeichenfolgen werden im permanenten Bereich des JVM-Heap-Speichers erstellt. Ja, sie sind wirklich unveränderlich und können nach der Erstellung nicht geändert werden. Denn im JVM gibt es drei Arten von Heap-Speicher: 1. Young generation 2. Old generation 3. Permanent generation.

Wenn ein Objekt erstellt wird, wird es in den Bereich des jungen Generation-Heap und den PermGen-Bereich für das String-Pooling verschoben.

Hier finden Sie weitere Details, aus denen Sie mehr Informationen erhalten können: Wie die Garbage Collection in Java funktioniert.

1voto

simbo1905 Punkte 5650

[Haftungsausschluss: Dies ist eine absichtlich meinungsstarke Art der Antwort, da ich der Meinung bin, dass eine eher "macht das bloß nicht zu Hause, Kinder" Antwort angebracht ist]

Die Sünde ist die Zeile field.setAccessible(true);, die besagt, dass das öffentliche API verletzt wird, indem der Zugriff auf ein privates Feld ermöglicht wird. Das ist ein riesiges Sicherheitsrisiko, das durch Konfigurieren eines Sicherheitsmanagers behoben werden kann.

Das Phänomen in der Frage sind Implementierungsdetails, die Sie niemals sehen würden, wenn Sie diese gefährliche Zeile mit Hilfe von Reflection nicht verwenden würden, um die Zugriffsmodifizierer zu verletzen. Klar ist, dass zwei (normalerweise) unveränderliche Strings dasselbe Zeichenarray teilen können. Ob ein Teilstring dasselbe Array teilt, hängt davon ab, ob er es kann und ob der Entwickler dachte, es zu teilen. Normalerweise handelt es sich um unsichtbare Implementierungsdetails, die Sie nicht kennen müssen, es sei denn, Sie schießen den Zugriffsmodifizierer mit dieser Zeile Code in den Kopf.

Es ist einfach keine gute Idee, sich auf solche Details zu verlassen, die ohne Verletzung der Zugriffsmodifizierer mittels Reflection nicht erfahrbar sind. Der Eigentümer dieser Klasse unterstützt nur das normale öffentliche API und ist frei, Implementierungsänderungen in der Zukunft vorzunehmen.

Trotz all dem ist die Zeile Code wirklich sehr nützlich, wenn Ihnen eine Waffe an den Kopf gehalten wird und Sie zu solch gefährlichen Dingen gezwungen werden. Die Verwendung dieses Hintereingangs ist in der Regel ein Codegeruch, der darauf hinweist, dass Sie auf bessere Bibliothekscode aufrüsten müssen, bei dem Sie nicht sündigen müssen. Eine weitere häufige Verwendung dieser gefährlichen Zeile Code ist die Entwicklung eines "Voodoo-Frameworks" (ORM, Injektionscontainer, ...). Viele Leute werden in Bezug auf solche Frameworks religiös (sowohl dafür als auch dagegen), daher werde ich darauf verzichten, einen Glaubenskrieg auszulösen, indem ich nichts weiter sage, als dass die überwältigende Mehrheit der Programmierer nicht dorthin gehen muss.

0voto

String ist von Natur aus unveränderlich, da es keine Methode gibt, um das String-Objekt zu ändern. Darum wurden StringBuilder und StringBuffer Klassen eingeführt

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