Was sind unveränderliche Objekte?
Ein String-Objekt ist unveränderlich, aber das bedeutet nur, dass Sie seinen Wert nicht durch Aufrufen seiner öffentlichen Methoden ändern können.
Wie wir alle wissen, ist die String-Klasse in Java unveränderlich. Was genau sind also unveränderliche Objekte? Man kann es sich so vorstellen: Wenn ein Objekt seinen Zustand nach seiner Erstellung nicht ändern kann, dann ist das Objekt unveränderlich. Der Status kann nicht geändert werden, was bedeutet, dass die Mitgliedsvariablen innerhalb des Objekts nicht geändert werden können, einschließlich der Werte grundlegender Datentypen. Variablen von Referenztypen können nicht auf andere Objekte verweisen, und der Status von Objekten, auf die durch Referenztypen verwiesen wird, kann nicht geändert werden geändert werden.
Unterscheiden Sie zwischen Objekten und Objektreferenzen
Für Java-Anfänger bestehen immer Zweifel daran, dass String ein unveränderliches Objekt ist. Schauen Sie sich den folgenden Code an:
String s = "ABCabc";System.out.println("s = " + s);s = "123456";System.out.println("s = " + s);Das gedruckte Ergebnis ist:
s = ABCabc s = 123456
Erstellen Sie zuerst ein String-Objekt s, lassen Sie dann den Wert von s „ABCabc“ und dann den Wert von s „123456“ sein. Wie aus den gedruckten Ergebnissen hervorgeht, hat sich der Wert von s tatsächlich geändert. Warum sagen Sie immer noch, dass String-Objekte unveränderlich sind? Tatsächlich liegt hier ein Missverständnis vor: s ist nur eine Referenz auf ein String-Objekt, nicht das Objekt selbst. Das Objekt ist ein Speicherbereich im Speicher. Je mehr Mitgliedsvariablen, desto größer ist der Platz, den dieser Speicherbereich einnimmt. Eine Referenz ist lediglich ein 4-Byte-Datenelement, das die Adresse des Objekts speichert, auf das es verweist. Über diese Adresse kann auf das Objekt zugegriffen werden.
Mit anderen Worten, s ist nur eine Referenz, die auf ein bestimmtes Objekt zeigt. Wenn s="123456" ausgeführt wird, wird ein neues Objekt „123456“ erstellt, und die Referenz s zeigt erneut auf dieses Herzobjekt , das ursprüngliche Objekt „ABCabc“ ist noch im Speicher vorhanden und hat sich nicht geändert. Die Speicherstruktur ist in der folgenden Abbildung dargestellt:
Ein Unterschied zwischen Java und C++ besteht darin, dass es in Java nicht möglich ist, das Objekt selbst direkt zu bedienen. Auf alle Objekte wird durch eine Referenz verwiesen. Sie müssen diese Referenz verwenden, um auf das Objekt selbst zuzugreifen, einschließlich des Abrufens und Änderns des Werts der Mitgliedsvariablen die Mitgliedsvariable des Objekts. In C++ gibt es drei Dinge: Referenzen, Objekte und Zeiger, die alle auf Objekte zugreifen können. Tatsächlich sind Referenzen in Java und Zeiger in C++ konzeptionell ähnlich. Sie sind die Adresswerte von im Speicher gespeicherten Objekten. In Java können Referenzen jedoch nicht wie Addition und Subtraktion verwendet werden werden wie Zeiger in C++ ausgeführt.
Warum sind String-Objekte unveränderlich?
Um die Unveränderlichkeit von String zu verstehen, werfen Sie zunächst einen Blick auf die Mitgliedsvariablen in der String-Klasse. In JDK1.6 umfassen die Mitgliedsvariablen von String Folgendes:
public final class String implementiert java.io.Serializable, Comparable<String>, CharSequence{ /** Der Wert wird für die Zeichenspeicherung verwendet */ private final char value[] /** Der Offset ist der erste Index der Speicherung das wird verwendet. */ private final int offset; /** Die Anzahl ist die Anzahl der Zeichen im String */ private int hash; Standardmäßig auf 0In JDK1.7 hat die String-Klasse einige Änderungen vorgenommen, hauptsächlich das Verhalten bei der Ausführung der Teilstring-Methode, was nichts mit dem Thema dieses Artikels zu tun hat. In JDK1.7 gibt es nur zwei Hauptmitgliedsvariablen der String-Klasse:
public final class String implementiert java.io.Serializable, Comparable<String>, CharSequence { /** Der Wert wird für die Zeichenspeicherung verwendet */ private final char value[] /** Hash-Code für die Zeichenfolge zwischenspeichern */ private int hash; // Standardmäßig 0Wie aus dem obigen Code ersichtlich ist, handelt es sich bei der String-Klasse in Java tatsächlich um eine Kapselung eines Zeichenarrays. In JDK6 ist value ein von String gekapseltes Array, offset ist die Startposition von String im Value-Array und count ist die Anzahl der von String belegten Zeichen. In JDK7 gibt es nur eine Wertvariable, dh alle Zeichen im Wert gehören zum String-Objekt. Diese Änderung hat keinen Einfluss auf die Diskussion dieses Artikels. Darüber hinaus gibt es eine Hash-Mitgliedsvariable, die einen Cache des Hash-Werts des String-Objekts darstellt. Diese Mitgliedsvariable ist für die Diskussion dieses Artikels ebenfalls irrelevant. In Java sind Arrays auch Objekte (siehe meinen vorherigen Artikel „Eigenschaften von Arrays in Java“). Der Wert ist also nur eine Referenz, die auf ein echtes Array-Objekt verweist. Tatsächlich sollte nach der Ausführung des Codes String s = „ABCabc“; das tatsächliche Speicherlayout wie folgt aussehen:
Die drei Variablen Wert, Offset und Anzahl sind alle privat und es werden keine öffentlichen Methoden wie setValue, setOffset und setCount zum Ändern dieser Werte bereitgestellt, sodass String nicht außerhalb der String-Klasse geändert werden kann. Das heißt, nach der Initialisierung kann es nicht mehr geändert werden und auf diese drei Mitglieder kann außerhalb der String-Klasse nicht zugegriffen werden. Darüber hinaus sind die drei Variablen Wert, Offset und Anzahl alle endgültig, was bedeutet, dass diese drei Werte innerhalb der String-Klasse nach der Initialisierung nicht mehr geändert werden können. Daher kann das String-Objekt als unveränderlich betrachtet werden.
In String gibt es also offensichtlich einige Methoden, durch deren Aufruf der geänderte Wert abgerufen werden kann. Zu diesen Methoden gehören Teilzeichenfolge, Ersetzen, ErsetzenAll, ToLowerCase usw. Zum Beispiel der folgende Code:
String a = "ABCabc"; System.out.println("a = " + a);Das gedruckte Ergebnis ist:
a = ABCabca = aBCabc
Dann scheint sich der Wert von a geändert zu haben, aber in Wirklichkeit handelt es sich um dasselbe Missverständnis. Auch hier ist a nur eine Referenz, kein echtes String-Objekt. Beim Aufruf von a.replace('A', 'a') erstellt die Methode intern ein neues String-Objekt und weist das neue Objekt Cited a neu zu. Der Quellcode der Ersetzungsmethode in String kann das Problem veranschaulichen:
Leser können andere Methoden selbst überprüfen. Sie alle erstellen ein neues String-Objekt innerhalb der Methode und geben dieses neue Objekt zurück. Das ursprüngliche Objekt wird nicht geändert. Aus diesem Grund haben Methoden wie „replace“, „substring“, „toLowerCase“ usw. alle Rückgabewerte. Dies ist auch der Grund, warum ein Aufruf wie der folgende den Wert des Objekts nicht ändert:
String ss = "123456";System.out.println("ss = " + ss);ss.replace('1', '0');System.out.println("ss = " + ss);Drucken Sie das Ergebnis aus:
ss = 123456 ss = 123456
Sind String-Objekte wirklich unveränderlich?
Wie aus dem Obigen ersichtlich ist, sind die Mitgliedsvariablen von String private Endvariablen, das heißt, sie können nach der Initialisierung nicht geändert werden. Unter diesen Mitgliedern ist der Wert etwas Besonderes, da es sich um eine Referenzvariable und nicht um ein reales Objekt handelt. value wird durch final geändert, was bedeutet, dass final nicht mehr auf andere Array-Objekte zeigen kann. Kann ich also das Array ändern, auf das value zeigt? Ändern Sie beispielsweise das Zeichen an einer bestimmten Position im Array in einen Unterstrich „_“. Zumindest können wir dies nicht in dem normalen Code tun, den wir selbst schreiben, da wir überhaupt nicht auf diese Wertreferenz zugreifen können, geschweige denn das Array über diese Referenz ändern können.
Wie können wir also auf private Mitglieder zugreifen? Richtig, mithilfe der Reflektion können Sie das Wertattribut im String-Objekt widerspiegeln und dann die Struktur des Arrays über die erhaltene Wertreferenz ändern. Hier ist der Beispielcode:
public static void testReflection() throws Exception { //Erstelle die Zeichenfolge „Hello World“ und weise sie der Referenz zu s String s = „Hello World“; System.out.println("s = " + s); World // Holen Sie sich das Wertfeld in der String-Klasse Field valueFieldOfString = String.class.getDeclaredField("value"); //Ändern Sie die Zugriffsberechtigung des Wertattributs valueFieldOfString.setAccessible(true); //Erhalten Sie den Wert des Wertattributs für das s-Objekt char[] value = (char[]) valueFieldOfString.get(s); //Ändern Sie das fünfte Zeichen im Array, auf das durch value verwiesen wird value[5] = '_' ; System.out.println("s = " + s);Das gedruckte Ergebnis ist:
s = Hallo Welt s = Hallo_Welt
In diesem Prozess bezieht sich s immer auf dasselbe String-Objekt, aber vor und nach der Reflektion ändert sich das String-Objekt. Mit anderen Worten, das sogenannte „unveränderliche“ Objekt kann durch Reflektion geändert werden. Aber im Allgemeinen machen wir das nicht. Dieses Reflexionsbeispiel kann auch ein Problem veranschaulichen: Wenn sich der Zustand eines Objekts und anderer Objekte, aus denen es besteht, ändern kann, dann ist dieses Objekt wahrscheinlich kein unveränderliches Objekt. Beispielsweise wird ein Car-Objekt mit einem Wheel-Objekt kombiniert. Obwohl das Wheel-Objekt als privat final deklariert ist, kann sich der interne Status des Wheel-Objekts ändern, sodass nicht garantiert werden kann, dass das Car-Objekt unveränderlich ist.