Ein Vorteil der Java -Sprache besteht darin, dass sie das Konzept der Zeiger abbricht, aber auch viele Programmierer dazu veranlasst, den Unterschied zwischen Objekten und Referenzen in der Programmierung häufig zu ignorieren. Dieser Artikel wird versuchen, dieses Konzept zu klären. Und da Java das Problem des Objekts, das durch einfache Zuordnung während des Entwicklungsprozesses kopiert wird, nicht lösen kann, ist es häufig erforderlich, die Clone () -Methode zu verwenden, um Objekte zu kopieren. Mit diesem Artikel können Sie verstehen, was Schattenklon und Deep -Klon sind, und ihre Unterschiede, Vor- und Nachteile verstehen.
Sind Sie ein wenig verwirrt, wenn Sie diesen Titel sehen: Die Java -Sprache besagt eindeutig, dass Zeiger abgesagt werden, da Zeiger oft bequem und auch die Hauptursache für Codeunsicherheit sind und das Programm auch sehr komplex und schwer zu verstehen macht. Der Code, der durch Missbrauch von Zeigern geschrieben wurde, ist nicht weniger als die bereits berüchtigte Erklärung "Goto". Es ist absolut weise für Java, das Konzept der Zeiger aufzugeben. Dies ist jedoch nur so, dass es in der Java -Sprache keine klare Zeigerdefinition gibt. Im Wesentlichen gibt jede neue Aussage einen Zeigerreferenz zurück. In den meisten Fällen muss Java jedoch keine Sorgen darüber machen, wie dieser "Zeiger" betrieben werden soll, geschweige denn so verängstigt zu sein wie beim Betrieb von Zeigern in C ++. Das einzige, was Sie mehr interessieren, ist das Übergeben von Objekten an Funktionen.
Paket com.zoer.src; public class objref {obj aObj = new obj (); int Aint = 11; public void changeObj (obj inobj) {inobj.str = "geänderter Wert"; } public void ChangePri (int inint) {inint = 22; } public static void main (String [] args) {objref oref = new objref (); System.out.println ("Before Call ChangeObj () Methode:" + oref.aobj); oref.changeobj (oref.aobj); System.out.println ("After Call ChangeObj () Methode:" + oref.aobj); System.out.println("=============================================================================================== =====================================================ieben =====================================================ieben =====================================================ieben CALL MEISSPRI () Methode: " + oref.aint); }} Paket com.zoer.src; public class obj {string str = "init value"; public String toString () {return str; }} Der Hauptteil dieses Codes nennt zwei sehr ähnliche Methoden, ChangeObj () und ChangePri (). Der einzige Unterschied besteht darin, dass ein Objekt als Eingabeparameter und der andere den Basistyp in Java als Eingabeparameter nimmt. Und die Eingabeparameter werden in beiden Funktionskörpern geändert. Die scheinbar gleiche Methode, aber die Ausgabeergebnisse des Programms sind unterschiedlich. Die ChangeObj () -Methode ändert wirklich die Eingabeparameter, während die ChangePri () -Methode die Eingabeparameter nicht ändert.
Aus diesem Beispiel verarbeitet Java Objekte und grundlegende Datentypen unterschiedlich. Wie C, wenn die grundlegenden Datentypen von Java (z. B. int, char, doppelt usw.) als Eintragsparameter an die Funktionskörper übergeben werden, werden die übergebenen Parameter zu lokalen Variablen innerhalb der Funktionskörper. Diese lokale Variable ist eine Kopie der Eingabeparameter. Alle Operationen innerhalb der Funktionsbehörde sind Operationen für diese Kopie. Nach Abschluss der Funktionsausführung wird die lokale Variable ihre Mission abschließen und die Variablen als Eingabeparameter nicht beeinflussen. Diese Methode des Parameterübergangs wird als "Wertpasser" bezeichnet. In Java ist der Pass, der ein Objekt als Eintragsparameter verwendet, "Referenzpass", was bedeutet, dass nur eine "Referenz" des Objekts übergeben wird. Das Konzept dieser "Referenz" entspricht der Zeigerreferenz in der C -Sprache. Wenn die Eingabevariable in der Funktionskörper geändert wird, handelt es sich im Wesentlichen um eine direkte Operation des Objekts.
Mit Ausnahme von "Referenzpass" beim Übergeben des Werts der Funktion "Referenzpass" bei der Zuweisung von Werten an Objektvariablen "=". Es ist ähnlich wie eine Variable einen weiteren Alias zu geben. Beide Namen deuten auf dasselbe Objekt im Speicher hin.
In der tatsächlichen Programmierung begegnen wir häufig auf diese Situation: Es gibt ein Objekt A, das in einem bestimmten Zeitpunkt einige gültige Werte enthält. Zu diesem Zeitpunkt kann ein neues Objekt B genau das gleiche wie A benötigt, und alle Änderungen an B beeinflussen nicht den Wert in A. Das heißt, A und B sind zwei unabhängige Objekte, aber der Anfangswert von B wird durch das A -Objekt bestimmt. In der Java -Sprache kann die Verwendung einfacher Zuweisungsanweisungen diese Anforderung nicht erfüllen. Obwohl es viele Möglichkeiten gibt, dieses Bedürfnis zu erfüllen, ist unter ihnen der einfachste und effizienteste Weg, um die Methode der Clone () zu implementieren.
Alle Java -Klassen erben standardmäßig die Java.lang.Object -Klasse, und es gibt eine Methode Clone () in der Klasse java.lang.object. Die JDK -API -Dokumentation erklärt, dass diese Methode eine Kopie des Objektobjekts zurückgibt. Es sind zwei Punkte zu erklären: Erstens gibt das Kopieobjekt ein neues Objekt zurück, keine Referenz. Zweitens besteht der Unterschied zwischen dem Kopieren eines Objekts und eines neuen Objekts mit dem neuen Bediener darin, dass diese Kopie bereits einige Informationen über das ursprüngliche Objekt und nicht die ersten Informationen des Objekts enthält.
Wie wenden Sie die Clone () -Methode an?
Ein sehr typischer Aufruf des Clone () -Codes ist wie folgt:
öffentliche Klasse Cloneclass implementiert klonbares {public int Aint; öffentliches Objekt Clone () {CloneClass o = null; try {o = (cloneclass) super.clone (); } catch (clonenotsUsPortedexception e) {e.printstacktrace (); } return o; }} Es sind drei Punkte erwähnenswert. Erstens implementiert die Kloneklassenklasse, die die Implementierung der Klonfunktion implementiert, die klonbare Schnittstelle. Diese Schnittstelle gehört zum Java.lang -Paket. Das Java.lang -Paket wurde in die Standardklasse importiert, sodass es nicht als java.lang.clonable geschrieben werden muss. Eine andere Sache, die erwähnenswert ist, ist, dass die Clone () -Methode überladen ist. Schließlich wird Super.clone () in der Clone () -Methode aufgerufen, was auch bedeutet, dass die Erbschaftsstruktur der Klonklasse, Super.clone () direkt oder indirekt die Clone () -Methode der Java.lang.Object -Klasse nennt. Erklären wir diese Punkte im Folgenden im Detail.
Es sollte gesagt werden, dass der dritte Punkt der wichtigste ist. Wenn Sie eine native Methode des Objektklassenclone () sorgfältig beobachten, ist die Effizienz der nativen Methode im Allgemeinen viel höher als die der nicht einheimischen Methoden in Java. Dies erklärt auch, warum wir die Clone () -Methode im Objekt anstelle der ersten neuen A -Klasse verwenden und dann die Informationen aus dem ursprünglichen Objekt dem neuen Objekt zuweisen, obwohl dies auch die Klonfunktion implementiert. Für den zweiten Punkt sollten wir auch beobachten, ob Clone () in der Objektklasse eine Methode mit einer geschützten Eigenschaft ist. Dies bedeutet auch, dass Sie die Objektklasse erben müssen, wenn Sie die Methode Clone () anwenden möchten. In Java erben alle Klassen standardmäßig die Objektklasse, sodass Sie sich darüber keine Sorgen machen müssen. Überladen Sie dann die Clone () -Methode. Eine andere Sache ist, dass die Attribute der Clone () -Methode nach der Überlastung nach Überlastung die Attribute der Clone () -Methode auf die Öffentlichkeit festgelegt werden müssen, damit andere Klassen die Clone () -Methode aufrufen können.
Warum implementiert die Klonklasse die klonbare Schnittstelle immer noch? Beachten Sie, dass die klonbare Schnittstelle keine Methoden enthält! Tatsächlich ist diese Schnittstelle nur ein Flag, und dieses Flag ist nur für die Clone () -Methode in der Objektklasse. Wenn die Klonklasse die klonbare Schnittstelle nicht implementiert und die Clone () -Methode des Objekts aufruft (dh die Super.clone () -Methode aufgerufen wird), wirft die Clone () -Methode des Objekts eine Ausnahme von Clonenotsupportededexception aus.
Die oben genannten sind die grundlegendsten Schritte des Klons. Wenn Sie einen erfolgreichen Klon absolvieren möchten, müssen Sie auch verstehen, was "Schattenklon" und "Deep Clone" sind.
Was ist Schattenklon?
Paket com.zoer.src; Klasse Unconea {private int i; public clonea (int ii) {i = ii; } public void DoubleValue () {i *= 2; } public String toString () {return Integer.toString (i); }} class CloneB implementiert klonbar {public int Aint; öffentliche Unconea UNCA = neue Unconea (111); öffentliches Objekt Clone () {Cloneb o = null; try {o = (CloneB) Super.clone (); } catch (clonenotsUsPortedexception e) {e.printstacktrace (); } return o; }} public class objref {public static void main (String [] a) {cloneB b1 = new Cloneb (); b1.aint = 11; System.out.println ("vor dem Klon, b1.aint =" + b1.aint); System.out.println ("vor dem Klon, b1.unca =" + b1.unca); CloneB B2 = (Cloneb) B1.clone (); b2.aint = 22; b2.unca.doubleValue (); System.out.println("======================================================================================================== =================================================================== =================================================================== =================================================================== =================================================================== =================================================================== =================================================================== Clone, b2.aint = " + b2.aint); System.out.println ("After Clone, b2.unca =" + b2.unca); }} Ausgangsergebnis:
Vor dem Klon, b1.aint = 11be -vor -Klon, B1.unca = 111 =========================================================== ================================================================= ================================================================= ================================================================= ================================================================= ================================================================= ================================================================= =================================================================
Die Ausgabeergebnisse zeigen, dass die INT -Variable AINT und das Klonergebnis des Instanzobjekts UNCA von Unconea inkonsistent sind. Der INT -Typ ist wirklich Klon, da die AINT -Variable in B2 keinen Einfluss auf die AINT von B1 hat. Das heißt, B2.AINT und B1.AINT BEREITEN SCHNEITIGEN METZUNGSBEREICHEN, B2.AINT ist eine echte Kopie von B1.ain. Im Gegenteil, die Änderung zu B2.unca ändert sich gleichzeitig zu b1.unca, und es ist offensichtlich, dass B2.unca und B1.unca unterschiedliche Referenzen sind, die nur auf dasselbe Objekt hinweisen! Daraus können wir sehen, dass der Effekt des Aufrufens der Clone () -Methode in der Objektklasse: Zuerst ein Stück Speicher im Speicher öffnen, das dem ursprünglichen Objekt entspricht, und dann den Inhalt im ursprünglichen Objekt so kopieren. Für grundlegende Datentypen sind solche Operationen nicht problematisch, aber für nicht primitive Typvariablen wissen wir, dass sie nur Verweise auf Objekte speichern, was auch dazu führt, dass die nicht primitiven Typvariablen nach dem Klon auf dasselbe Objekt und die entsprechenden Variablen im ursprünglichen Objekt hinweisen.
Meistens sind die Ergebnisse dieses Klons oft nicht die Ergebnisse, auf die wir hoffen, und dieser Klon wird auch als "Schattenklon" bezeichnet. Um B2.Unca auf ein von B2.unca andersem Objekt zu zeigen, und B2.unca enthält auch Informationen in B1.unca als erste Informationen, müssen Sie einen Tiefenklon implementieren.
Wie führe ich einen tiefen Klon aus?
Es ist sehr einfach, das obige Beispiel in einen tiefen Klon zu ändern, und zwei Änderungen sind erforderlich: Eine besteht darin, der Unklega -Klasse die gleiche Klonfunktion wie die CloneB -Klasse zu implementieren (implementieren Sie die Klonschnittstelle und überlasten Sie die Clone () -Methode). Die zweite besteht darin, die Clone () -Methode von CloneB einen Satz zu fügen.
Paket com.zoer.src; Klasse Unconea implementiert klonbar {private int i; public clonea (int ii) {i = ii; } public void DoubleValue () {i *= 2; } public String toString () {return Integer.toString (i); } public Object Clone () {cronea o = null; try {o = (clonea) super.clone (); } catch (clonenotsUsPortedexception e) {e.printstacktrace (); } return o; }} class CloneB implementiert klonbar {public int Aint; öffentliche Unconea UNCA = neue Unconea (111); öffentliches Objekt Clone () {Cloneb o = null; try {o = (CloneB) Super.clone (); } catch (clonenotsUsPortedexception e) {e.printstacktrace (); } o.unca = (clonea) unca.clone (); Rückkehr O; }} öffentliche Klasse Clonemain {public static void main (String [] a) {Cloneb b1 = new Cloneb (); b1.aint = 11; System.out.println ("vor dem Klon, b1.aint =" + b1.aint); System.out.println ("vor dem Klon, b1.unca =" + b1.unca); CloneB B2 = (Cloneb) B1.clone (); b2.aint = 22; b2.unca.doubleValue (); System.out.println("================================================================================ ==========================================================ieben ==========================================================ieben ===========================================================ieben Clone, b1.aint = " + b1.aint); System.out.println ("After Clone, b1.unca =" + b1.unca); System.out.println("=================================================================================================== =======================================================ieben ================================================================= =======================================================ieben ================================================================= =======================================================ieben ================================================================= Ausgangsergebnis:
Vor dem Klon, b1.aint = 11be -vor -Klon, B1.unca = 111 =========================================================== ================================================================= ================================================================= ================================================================= ================================================================= ================================================================= ================================================================= =================================================================
Es ist ersichtlich, dass die aktuelle Änderung von B2.unca keinen Einfluss auf b1.unca hat. Zu diesem Zeitpunkt weisen B1.unca und B2.unca auf zwei verschiedene Unconea -Instanzen hin, und B1 und B2 haben im Moment den gleichen Wert, wenn Cloneb B2 = (Cloneb) b1.clone (); wird hier genannt, b1.i = b2.i = 11.
Sie sollten wissen, dass nicht alle Klassen tiefe Klone implementieren können. Wenn Sie beispielsweise die Variable der Unklöpfertyp in der obigen CloneB -Klasse in den StringBuffer -Typ ändern, sehen Sie sich die Beschreibung zu StringBuffer in der JDK -API an. StringBuffer überlastet die Clone () -Methode nicht, und was ernsthafter ist, ist, dass StringBuffer noch eine endgültige Klasse ist, was bedeutet, dass wir den Klon von StringBuffer mithilfe der Erbschaft nicht indirekt implementieren können. Wenn eine Klasse ein StringBuffer -Objekt oder ein ähnliches Objekt enthält, das StringBuffer ähnelt, haben wir zwei Möglichkeiten: Entweder kann nur Schattenklon implementiert werden, oder fügen Sie der Clone () -Methode der Klasse einen Satz hinzu (unter der Annahme, dass es sich um ein SningBuffer -Objekt handelt, und der variable Name ist immer noch unca); // das ursprüngliche ist: o.unca = (clonea) unca.clone ();
Es sollte auch beachtet werden, dass das String -Objekt zusätzlich zu den grundlegenden Datentypen, die automatisch tiefe Klone implementieren können, eine Ausnahme ist. Die Leistung nach dem Klon scheint auch tiefe Klone zu implementieren. Obwohl dies nur eine Illusion ist, erleichtert es unsere Programmierung stark.
Der Unterschied zwischen String und StringBuffer im Klon sollte erklärt werden, dass dies kein Fokus auf den Unterschied zwischen String und StringBuffer ist, aber aus diesem Beispiel können wir auch einige eindeutige Funktionen der String -Klasse sehen.
Das folgende Beispiel enthält zwei Klassen. Die Clonec -Klasse enthält eine String -Typ -Variable und eine StringBuffer -Typvariable und implementiert die Clone () -Methode. Die Variable C1 der Clonec -Art wird in der Strclone -Klasse deklariert, und dann wird die C1 -Methode von Clone () aufgerufen, um eine Kopie von C1 C2 zu generieren. Nach dem Ändern der Variablen für Zeichenfolge und StringBuffer -Typ in C2 wird das Ergebnis gedruckt:
Paket com.zoer.src; Klasse Clonec implementiert klonbar {public String str; public StringBuffer strbuff; öffentliches Objekt Clone () {Clonec o = null; try {o = (clonec) super.clone (); } catch (clonenotsUsPortedexception e) {e.printstacktrace (); } return o; }} public class Strclone {public static void main (String [] a) {clonec c1 = new clonec (); c1.str = new String ("initializestern"); c1.strbuff = new StringBuffer ("initializestBBuff"); System.out.println ("vor dem Klon, c1.str =" + c1.str); System.out.println ("vor dem Klon, c1.strbuff =" + c1.strbuff); Clonec C2 = (Clonec) c1.clone (); c2.str = c2.str.substring (0, 5); c2.strbuff = c2.strbuff.Append ("Strbuff Clone ändern"); System.out.println ("================================================= ====================================================================== ====================================================================== ======================================================================= ====================================================================== ====================================================================== ======================================================================== ======================================================================= System.out.println ("After Clone, c2.strbuff =" + c2.strbuff); Ausführungsergebnisse:
<span style = "Schriftfamilie: 'Microsoft Yahei';"> <span style = "Schriftgröße: 16px;"> vor dem Klon, c1.str = initializesters vor dem klon, c1.strbuff = initializestrBuff =================================================================ieben =================================================================ieben =================================================================ieben =================================================================ieben Klon, c2.str = initializestrBuff Änderung strbuff klon </span> </span>
Das gedruckte Ergebnis zeigt, dass die Variablen des String -Typs anscheinend der Tiefenklon implementiert zu haben scheinen, da die Änderungen an C2.Str C1.str nicht beeinflusst haben! Betrachtet Java die Sring -Klasse als grundlegende Datentyp? Tatsächlich ist dies nicht der Fall. Hier gibt es einen kleinen Trick. Das Geheimnis liegt in der Aussage c2.str = c2.str.Substring (0,5)! Im Wesentlichen sind C1.SR und C2.SR immer noch Referenzen, wenn es klon ist, und beide weisen auf dasselbe String -Objekt hin. Wenn jedoch c2.str = c2.str.Substring (0,5) ist, ist es gleichwertig, einen neuen String -Typ zu generieren und ihn dann zurück zu C2.str zuzuweisen. Dies liegt daran, dass String von Sun Engineers als unveränderliche Klasse geschrieben wird und Funktionen in allen String -Klassen ihre eigenen Werte nicht ändern können.
Das obige dreht sich alles um diesen Artikel. Ich hoffe, es wird für alle hilfreich sein, die tiefe und flache Replikation in Java zu verstehen.