Ist eine Java-Zeichenfolge wirklich unveränderlich?


399

Wir alle wissen, dass dies Stringin Java unveränderlich ist, überprüfen Sie jedoch den folgenden Code:

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 ist der Wert von s1und s2verändert, aber nicht s3?


394
Sie können alle Arten von dummen Tricks mit Reflexion machen. Aber Sie brechen im Grunde den Aufkleber "Garantie ungültig, wenn entfernt" auf der Klasse, sobald Sie es tun.
CHao

16
@ DarshanPatel verwenden Sie einen SecurityManager, um die Reflexion zu deaktivieren
Sean Patrick Floyd

39
Wenn Sie wirklich mit Dingen herumspielen möchten, können Sie dies so gestalten, dass Sie (Integer)1+(Integer)2=42mit dem zwischengespeicherten Autoboxing herumspielen. (Disgruntled-Bomb-Java-Edition) ( thedailywtf.com/Articles/Disgruntled-Bomb-Java-Edition.aspx )
Richard Tingle

15
Diese Antwort, die ich vor fast 5 Jahren geschrieben habe, könnte Sie amüsieren. Stackoverflow.com/a/1232332/27423 - Es geht um unveränderliche Listen in C #, aber im Grunde ist es dasselbe: Wie kann ich Benutzer davon abhalten, meine Daten zu ändern ? Und die Antwort ist, Sie können nicht; Reflexion macht es sehr einfach. Eine Mainstream-Sprache, die dieses Problem nicht hat, ist JavaScript, da es kein Reflexionssystem gibt, das auf lokale Variablen innerhalb eines Abschlusses zugreifen kann. Privat bedeutet also wirklich privat (obwohl es kein Schlüsselwort dafür gibt!)
Daniel Earwicker

49
Liest jemand die Frage bis zum Ende? Die Frage ist, lassen Sie mich bitte wiederholen: "Warum funktioniert dieses Programm so? Warum wird der Wert von s1 und s2 für s3 geändert und nicht geändert?" Die Frage ist NICHT, warum s1 und s2 geändert werden! Die Frage ist: WARUM wird s3 nicht geändert?
Roland Pihlakas

Antworten:


403

String ist unveränderlich *, aber dies bedeutet nur, dass Sie es nicht mit seiner öffentlichen API ändern können.

Was Sie hier tun, ist die Umgehung der normalen API mithilfe von Reflektion. Auf die gleiche Weise können Sie die Werte von Aufzählungen ändern, die beim Integer-Autoboxing verwendete Nachschlagetabelle ändern usw.

Der Grund s1und der s2Änderungswert besteht nun darin, dass beide auf dieselbe internierte Zeichenfolge verweisen. Der Compiler tut dies (wie in anderen Antworten erwähnt).

Der Grund dafür s3ist nicht eigentlich ein bisschen zu mir überraschend, da ich dachte , es würde den Anteil valueArray ( es hat in früheren Version von Java , bevor Java 7u6). Wenn Stringwir uns jedoch den Quellcode von ansehen, können wir sehen, dass das valueZeichenarray für einen Teilstring tatsächlich kopiert wird (mit Arrays.copyOfRange(..)). Deshalb bleibt es unverändert.

Sie können einen installieren SecurityManager, um bösartigen Code zu vermeiden, um solche Dinge zu tun. Beachten Sie jedoch, dass einige Bibliotheken von der Verwendung dieser Art von Reflexionstricks abhängen (normalerweise ORM-Tools, AOP-Bibliotheken usw.).

*) Ich schrieb anfangs, dass Strings nicht wirklich unveränderlich sind, sondern nur "effektiv unveränderlich". Dies kann in der aktuellen Implementierung von irreführend sein String, wenn das valueArray tatsächlich markiert ist private final. Es ist jedoch immer noch erwähnenswert, dass es keine Möglichkeit gibt, ein Array in Java als unveränderlich zu deklarieren. Daher muss darauf geachtet werden, dass es auch mit den richtigen Zugriffsmodifikatoren nicht außerhalb seiner Klasse verfügbar gemacht wird.


Da dieses Thema überwältigend beliebt zu sein scheint, wird hier eine weitere Lektüre vorgeschlagen: Heinz Kabutz 'Reflection Madness-Vortrag aus der JavaZone 2009, der viele Themen im OP zusammen mit anderen Überlegungen behandelt ... na ja ... Wahnsinn.

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


7
Tatsächlich ist das StringInternieren Teil des JLS ( "ein String-Literal bezieht sich immer auf dieselbe Instanz der Klasse String" ). Aber ich stimme zu, es ist keine gute Praxis, sich auf die Implementierungsdetails der StringKlasse zu verlassen.
HaraldK

3
Vielleicht liegt der Grund dafür, dass substringKopien statt eines "Abschnitts" des vorhandenen Arrays verwendet werden, darin, dass das riesige Array am Leben bleiben würde, wenn ich einen riesigen String hätte sund einen winzigen Teilstring herausnehmen würde, der tvon ihm aufgerufen wurde , und ihn später aufgab, saber behielt t(kein Müll gesammelt). Vielleicht ist es natürlicher, wenn jedem Zeichenfolgenwert ein eigenes Array zugeordnet ist?
Jeppe Stig Nielsen

10
Das Teilen von Arrays zwischen einem String und seinen Teilzeichenfolgen implizierte auch, dass jede String Instanz Variablen tragen musste, um den Offset in das angegebene Array und die angegebene Länge zu speichern. Dies ist ein Overhead, den man angesichts der Gesamtzahl der Zeichenfolgen und des typischen Verhältnisses zwischen normalen Zeichenfolgen und Teilzeichenfolgen in einer Anwendung nicht ignorieren sollte. Da sie für jede Zeichenfolgenoperation ausgewertet werden mussten, bedeutete dies, dass jede Zeichenfolgenoperation nur zum Vorteil einer einzigen Operation, einer billigen Teilzeichenfolge , verlangsamt wurde .
Holger

2
@Holger - Ja, ich verstehe, dass das Offset-Feld in den letzten JVMs gelöscht wurde. Und selbst wenn es vorhanden war, wurde es nicht so oft verwendet.
Hot Licks

2
@supercat: Es spielt keine Rolle, ob Sie nativen Code haben oder nicht, ob Sie unterschiedliche Implementierungen für Zeichenfolgen und Teilzeichenfolgen in derselben JVM haben oder ob Sie byte[]Zeichenfolgen für ASCII-Zeichenfolgen und char[]für andere haben. Dies bedeutet, dass bei jeder Operation überprüft werden muss, um welche Art von Zeichenfolge es sich handelt Betriebs. Dies behindert das Einfügen des Codes in die Methoden mithilfe von Zeichenfolgen. Dies ist der erste Schritt weiterer Optimierungen unter Verwendung der Kontextinformationen des Aufrufers. Dies ist eine große Auswirkung.
Holger

93

Wenn in Java zwei primitive Zeichenfolgenvariablen 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

Initialisierung

Aus diesem Grund gibt der Vergleich true zurück. Die dritte Zeichenfolge wird erstellt, mit substring()der eine neue Zeichenfolge erstellt wird, anstatt auf dieselbe zu zeigen.

Unterzeichenfolge

Wenn Sie mit Reflektion auf eine Zeichenfolge zugreifen, erhalten Sie den tatsächlichen Zeiger:

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

Wenn Sie dies ändern, wird die Zeichenfolge geändert, die einen Zeiger darauf enthält. Da dies jedoch s3mit einer neuen Zeichenfolge erstellt wird, substring()ändert sich dies nicht.

Veränderung


Dies funktioniert nur für Literale und ist eine Optimierung zur Kompilierungszeit.
SpacePrez

2
@ Zaphod42 Nicht wahr. Sie können interneine nicht-wörtliche Zeichenfolge auch manuell aufrufen und die Vorteile nutzen.
Chris Hayes

Beachten Sie jedoch: Sie möchten mit internBedacht verwenden. Wenn Sie alles internieren, gewinnen Sie nicht viel und können einige kratzende Momente verursachen, wenn Sie der Mischung Reflexion hinzufügen.
CHao

Test1und Test1sind nicht mit test1==test2den Java-Namenskonventionen vereinbar und folgen diesen nicht.
c0der

50

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

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

Es gibt Anwendungsfälle, in denen diese Art von Code zu Ihrem Vorteil verwendet werden kann und eine "gute Codierung" darstellt, z. B. das Löschen von Kennwörtern aus dem Speicher zum frühestmöglichen Zeitpunkt (vor der GC) .

Abhängig vom Sicherheitsmanager können Sie Ihren Code möglicherweise nicht ausführen.


30

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


24

Sichtbarkeitsmodifikatoren und final (dh Unveränderlichkeit) sind keine Messung gegen schädlichen Code in Java. Sie sind lediglich Werkzeuge, um sich vor Fehlern zu schützen und den Code wartbarer zu machen (eines der großen Verkaufsargumente des Systems). Aus diesem Grund können Sie Stringüber Reflection auf interne Implementierungsdetails wie das Backing Char Array für s zugreifen.

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

#substringBis vor kurzem funktionierte (Java 7u6) auf ähnliche Weise, was das Verhalten in der Originalversion Ihrer Frage erklärt hätte. Es wurde kein neues Backing-Char-Array erstellt, sondern das aus dem ursprünglichen String wiederverwendet. Es wurde gerade ein neues String-Objekt erstellt, das einen Versatz und eine Länge verwendete, um nur einen Teil dieses Arrays darzustellen. Dies funktionierte im Allgemeinen, da Strings unveränderlich sind - es sei denn, Sie umgehen dies. Diese Eigenschaft von #substringbedeutete auch, dass der gesamte ursprüngliche String nicht als Müll gesammelt werden konnte, wenn noch ein kürzerer Teilstring vorhanden war, der daraus erstellt wurde.

Ab dem aktuellen Java und Ihrer aktuellen Version der Frage gibt es kein merkwürdiges Verhalten von #substring.


2
Sichtbarkeitsmodifikatoren sind (oder waren zumindest) als Schutz gegen bösartigen Code gedacht. Sie müssen jedoch einen SecurityManager (System.setSecurityManager ()) festlegen, um den Schutz zu aktivieren. Wie sicher dies tatsächlich ist, ist eine andere Frage ...
Sleske

2
Verdient eine positive Bewertung, da Sie betonen, dass Zugriffsmodifikatoren nicht dazu gedacht sind , Code zu schützen. Dies scheint sowohl in Java als auch in .NET weitgehend missverstanden zu werden. Obwohl der vorherige Kommentar dem widerspricht; Ich weiß nicht viel über Java, aber in .NET ist dies sicherlich wahr. In keiner Sprache sollten Benutzer davon ausgehen, dass dies ihren Code hackfest macht.
Tom W

Es ist nicht möglich, den Vertrag finalauch durch Reflexion zu verletzen . Wie in einer anderen Antwort erwähnt, #substringteilen Java 7u6 keine Arrays.
ntoskrnl

Tatsächlich hat sich das Verhalten von finalim Laufe der Zeit geändert ...: -O Laut dem "Reflection Madness" -Vortrag von Heinz, den ich im anderen Thread gepostet habe, finalbedeutete dies endgültig in JDK 1.1, 1.3 und 1.4, konnte aber immer mit Reflection unter Verwendung von 1.2 geändert werden und in 1.5 und 6 in den meisten Fällen ...
haraldK

1
finalFelder können durch nativeCode geändert werden , wie dies vom Serialisierungsframework beim Lesen der Felder einer serialisierten Instanz durchgeführt wird und System.setOut(…)das die endgültige System.outVariable ändert . Letzteres ist das interessanteste Merkmal, da die Reflexion mit Zugriffsüberschreibung die static finalFelder nicht ändern kann .
Holger

11

Die Unveränderlichkeit von Zeichenfolgen erfolgt aus Sicht der Benutzeroberfläche. Sie verwenden Reflection, um die Schnittstelle zu umgehen und die Interna der String-Instanzen direkt zu ändern.

s1und s2werden beide geändert, weil sie beide derselben "internen" String-Instanz zugewiesen sind. Weitere Informationen zu diesem Teil finden Sie in diesem Artikel über String-Gleichheit und Internierung. Sie könnten überrascht sein , dass in Ihrem Beispielcode , um herauszufinden, s1 == s2kehrt true!


10

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

Zitieren aus der internen Zeichenfolgendarstellung von Oracle Tunes Java :

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

Mit dieser Änderung kann es ohne Reflexion geschehen (???).


2
Wenn das OP eine ältere Sun / Oracle-JRE verwenden würde, würde die letzte Anweisung "Java!" (wie er versehentlich gepostet hat). Dies wirkt sich nur auf die gemeinsame Nutzung des Wertearrays zwischen Zeichenfolgen und Unterzeichenfolgen aus. Sie können den Wert immer noch nicht ohne Tricks wie Reflexion ändern.
HaraldK

7

Hier gibt es wirklich zwei Fragen:

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

Zu Punkt 1: Mit Ausnahme des ROM befindet sich auf Ihrem Computer kein unveränderlicher Speicher. Heutzutage ist sogar ROM manchmal beschreibbar. Es gibt immer irgendwo Code (egal ob es sich um den Kernel oder den nativen Code handelt, der Ihre verwaltete Umgebung umgeht), der in Ihre Speicheradresse schreiben kann. Also, in "Realität", nein, sie sind nicht absolut unveränderlich.

Zu Punkt 2: Dies liegt daran, dass der Teilstring wahrscheinlich eine neue Zeichenfolgeninstanz zuweist, die wahrscheinlich das Array kopiert. Es ist möglich, Teilzeichenfolgen so zu implementieren, dass keine Kopie erstellt wird, dies bedeutet jedoch nicht, dass dies der Fall ist. Es gibt Kompromisse.

Sollte beispielsweise eine Referenz gehalten werden, reallyLargeString.substring(reallyLargeString.length - 2)um eine große Menge an Speicher am Leben zu erhalten, oder nur wenige Bytes?

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

In jedem Fall scheint Ihre JVM Deep Copies für Teilstring-Aufrufe verwendet zu haben.


3
Real ROM ist genauso unveränderlich wie ein in Plastik gehüllter Fotodruck. Das Muster wird dauerhaft festgelegt, wenn der Wafer (oder Druck) chemisch entwickelt wird. Elektrisch veränderbare Speicher, einschließlich RAM-Chips , können sich als "echtes" ROM verhalten, wenn die zum Schreiben erforderlichen Steuersignale nicht erregt werden können, ohne zusätzliche elektrische Verbindungen zu der Schaltung hinzuzufügen, in der sie installiert sind. Es ist eigentlich nicht ungewöhnlich, dass eingebettete Geräte RAM enthalten, das werkseitig eingestellt und von einer Pufferbatterie gewartet wird und dessen Inhalt werkseitig neu geladen werden muss, wenn die Batterie ausfällt.
Supercat

3
@supercat: Ihr Computer ist jedoch nicht eines dieser eingebetteten Systeme. :) Echte fest verdrahtete ROMs sind in PCs seit ein oder zwei Jahrzehnten nicht mehr üblich. Alles ist EEPROM und Flash in diesen Tagen. Grundsätzlich bezieht sich jede vom Benutzer sichtbare Adresse, die sich auf den Speicher bezieht, auf potenziell beschreibbaren Speicher.
CHao

@cHao: Viele Flash-Chips ermöglichen es, Teile auf eine Weise schreibgeschützt zu machen, die, wenn sie überhaupt rückgängig gemacht werden können, das Anlegen anderer Spannungen erfordert als für den normalen Betrieb (für die Motherboards nicht ausgerüstet wären). Ich würde erwarten, dass Motherboards diese Funktion nutzen. Außerdem bin ich mir bei den heutigen Computern nicht sicher, aber in der Vergangenheit hatten einige Computer einen RAM-Bereich, der während der Startphase schreibgeschützt war und nur durch einen Reset ungeschützt werden konnte (was die Ausführung zwingen würde, vom ROM aus zu starten).
Supercat

2
@supercat Ich denke, Sie verpassen den Punkt des Themas, nämlich dass die im RAM gespeicherten Zeichenfolgen niemals wirklich unveränderlich sein werden.
Scott Wisniewski

5

Um die Antwort von @ haraldK zu ergänzen: Dies ist ein Sicherheitshack, der zu schwerwiegenden Auswirkungen auf die App führen kann.

Als erstes wird eine konstante Zeichenfolge geändert, die in einem Zeichenfolgenpool gespeichert ist. Wenn eine Zeichenfolge als deklariert wird String s = "Hello World";, wird sie zur weiteren potenziellen Wiederverwendung in einen speziellen Objektpool eingefügt. Das Problem ist, dass der Compiler zur Kompilierungszeit einen Verweis auf die geänderte Version platziert. Sobald der Benutzer die in diesem Pool zur Laufzeit gespeicherte Zeichenfolge ändert, verweisen alle Verweise im Code auf die geänderte Version. Dies würde zu einem folgenden Fehler führen:

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

Wird drucken:

Hello Java!

Es gab ein anderes Problem, das ich hatte, als ich eine schwere Berechnung über solch riskante Zeichenfolgen implementierte. Es gab einen Fehler, der während der Berechnung etwa 1 von 1000000 Mal auftrat und das Ergebnis unbestimmt machte. Ich konnte das Problem durch Ausschalten der JIT finden - bei ausgeschalteter JIT wurde immer das gleiche Ergebnis erzielt. Ich vermute, dass der Grund dieser String-Sicherheits-Hack war, der einige der JIT-Optimierungsverträge gebrochen hat.


Möglicherweise handelte es sich um ein Thread-Sicherheitsproblem, das durch eine langsamere Ausführungszeit und weniger Parallelität ohne JIT maskiert wurde.
Ted Pennings

@ TedPennings Nach meiner Beschreibung wollte ich einfach nicht zu sehr auf Details eingehen. Ich habe tatsächlich ein paar Tage damit verbracht, es zu lokalisieren. Es war ein Single-Threaded-Algorithmus, der den Abstand zwischen zwei Texten in zwei verschiedenen Sprachen berechnete. Ich habe zwei mögliche Lösungen für das Problem gefunden - eine bestand darin, die JIT auszuschalten, und die zweite darin, buchstäblich No-Op hinzuzufügenString.format("") in eine der inneren Schleifen einzufügen. Es besteht die Möglichkeit, dass es sich um ein anderes als ein JIT-Fehler handelt, aber ich glaube, es war ein JIT, da dieses Problem nach dem Hinzufügen dieses No-Op nie wieder reproduziert wurde.
Andrey Chaschev

Ich habe dies mit einer frühen Version von JDK ~ 7u9 gemacht, also könnte es sein.
Andrey Chaschev

1
@Andrey Chaschev: "Ich habe zwei mögliche Korrekturen für das Problem gefunden" ... die dritte mögliche Lösung, nicht in die zu hacken String Interna , ist Ihnen nicht in den Sinn gekommen?
Holger

1
@ Ted Pennings: Thread-Sicherheitsprobleme und JIT-Probleme sind oft gleich. Die JIT darf Code generieren, der sich auf die finalSicherheitsgarantien des Feldthreads stützt , die beim Ändern der Daten nach der Objektkonstruktion brechen. Sie können es also nach Belieben als JIT- oder MT-Problem anzeigen. Das eigentliche Problem besteht darin, StringDaten zu hacken und zu ändern, von denen erwartet wird, dass sie unveränderlich sind.
Holger

5

Gemäß dem Konzept des Poolings verweisen alle String-Variablen, die denselben Wert enthalten, auf dieselbe Speicheradresse. Daher zeigen s1 und s2, die beide den gleichen Wert von „Hello World“ enthalten, auf denselben Speicherort (z. B. M1).

Auf der anderen Seite enthält s3 "Welt", daher weist es auf eine andere Speicherzuordnung hin (z. B. M2).

Was jetzt passiert ist, dass der Wert von S1 geändert wird (unter Verwendung des char [] -Werts). Daher wurde der Wert an dem Speicherplatz M1, auf den sowohl s1 als auch s2 zeigen, geändert.

Infolgedessen wurde der Speicherort M1 modifiziert, was eine Änderung des Wertes von s1 und s2 bewirkt.

Der Wert des Ortes M2 bleibt jedoch unverändert, daher enthält s3 den gleichen ursprünglichen Wert.


5

Der Grund, warum sich s3 nicht ändert, ist, dass in Java beim Erstellen eines Teilstrings das Wertzeichenarray für einen Teilstring intern kopiert wird (mithilfe von Arrays.copyOfRange ()).

s1 und s2 sind identisch, da beide in Java auf dieselbe internierte Zeichenfolge verweisen. Es ist beabsichtigt in Java.


2
Wie hat diese Antwort etwas zu den Antworten vor Ihnen hinzugefügt?
Grau

Beachten Sie auch, dass dies ein völlig neues Verhalten ist und von keiner Spezifikation garantiert wird.
Paŭlo Ebermann

Die Implementierung wurde String.substring(int, int)mit Java 7u6 geändert. Vor 7u6 würde die JVM halten nur einen Zeiger auf den ursprünglichen String‚s char[]zusammen mit einem Index und Länge. Nach 7u6 wird der Teilstring in einen neuen kopiert. StringEs gibt Vor- und Nachteile.
Eric Jablow

2

String ist unveränderlich, aber durch Reflexion können Sie die String-Klasse ändern. Sie haben gerade die String-Klasse in Echtzeit als veränderbar neu definiert. Sie können Methoden neu definieren, um öffentlich oder privat oder statisch zu sein, wenn Sie möchten.


2
Wenn Sie die Sichtbarkeit von Feldern / Methoden ändern, ist dies nicht sinnvoll, da diese zur Kompilierungszeit privat sind
Bohemian

1
Sie können die Zugänglichkeit von Methoden ändern, aber Sie können ihren öffentlichen / privaten Status nicht ändern und sie nicht statisch machen.
Grau

1

[Haftungsausschluss Dies ist eine absichtlich meinungsgebundene Antwort, da ich der Meinung bin, dass eine Antwort "Mach das nicht zu Hause, Kinder" gerechtfertigt ist.]

Die Sünde ist die Linie field.setAccessible(true); die besagt, dass die öffentliche API verletzt werden soll, indem der Zugang zu einem privaten Feld ermöglicht wird. Das ist eine riesige Sicherheitslücke, die durch die Konfiguration eines Sicherheitsmanagers geschlossen werden kann.

Das Phänomen in der Frage sind Implementierungsdetails, die Sie niemals sehen würden, wenn Sie diese gefährliche Codezeile nicht verwenden würden, um die Zugriffsmodifikatoren durch Reflexion zu verletzen. Es ist klar, dass zwei (normalerweise) unveränderliche Zeichenfolgen dasselbe Zeichenarray verwenden können. Ob ein Teilstring dasselbe Array verwendet, hängt davon ab, ob dies möglich ist und ob der Entwickler daran gedacht hat, es gemeinsam zu nutzen. Normalerweise sind dies unsichtbare Implementierungsdetails, die Sie nicht kennen sollten, es sei denn, Sie schießen den Zugriffsmodifikator mit dieser Codezeile durch den Kopf.

Es ist einfach keine gute Idee, sich auf solche Details zu verlassen, die nicht erlebt werden können, ohne die Zugriffsmodifikatoren durch Reflexion zu verletzen. Der Eigentümer dieser Klasse unterstützt nur die normale öffentliche API und kann künftig Implementierungsänderungen vornehmen.

Abgesehen davon ist die Codezeile wirklich sehr nützlich, wenn Sie eine Waffe haben, die Sie am Kopf hält und Sie dazu zwingt, so gefährliche Dinge zu tun. Die Verwendung dieser Hintertür ist normalerweise ein Codegeruch, den Sie aktualisieren müssen, um einen besseren Bibliothekscode zu erhalten, bei dem Sie nicht sündigen müssen. Eine andere häufige Verwendung dieser gefährlichen Codezeile ist das Schreiben eines "Voodoo-Frameworks" (Orm, Injektionscontainer, ...). Viele Leute werden religiös in Bezug auf solche Rahmenbedingungen (sowohl für als auch gegen sie), daher werde ich es vermeiden, einen Flammenkrieg einzuladen, indem ich nichts anderes sage, als dass die überwiegende Mehrheit der Programmierer nicht dorthin gehen muss.


1

Zeichenfolgen werden im permanenten Bereich des JVM-Heapspeichers erstellt. Also ja, es ist wirklich unveränderlich und kann nach der Erstellung nicht mehr geändert werden. Denn in der JVM gibt es drei Arten von Heapspeicher: 1. Junge Generation 2. Alte Generation 3. Permanente Generation.

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

Hier finden Sie weitere Informationen, aus denen Sie weitere Informationen abrufen können: Funktionsweise der Garbage Collection in Java .


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.