Ich muss die Unterschiede zwischen Deep Copy, Flat Copy und Clone in Java klären
Ich muss die Unterschiede zwischen Deep Copy, Flat Copy und Clone in Java klären
Antworten:
Leider sind "flache Kopie", "tiefe Kopie" und "Klon" allesamt ziemlich schlecht definierte Begriffe.
Im Java-Kontext müssen wir zunächst zwischen "Kopieren eines Werts" und "Kopieren eines Objekts" unterscheiden.
int a = 1;
int b = a; // copying a value
int[] s = new int[]{42};
int[] t = s; // copying a value (the object reference for the array above)
StringBuffer sb = new StringBuffer("Hi mom");
// copying an object.
StringBuffer sb2 = new StringBuffer(sb);
Kurz gesagt, eine Zuweisung einer Referenz zu einer Variablen, deren Typ ein Referenztyp ist, "kopiert einen Wert", wobei der Wert die Objektreferenz ist. Um ein Objekt zu kopieren, muss etwas new
explizit oder unter der Haube verwendet werden.
Nun zum "flachen" versus "tiefen" Kopieren von Objekten. Flaches Kopieren bedeutet im Allgemeinen, nur eine Ebene eines Objekts zu kopieren, während tiefes Kopieren im Allgemeinen das Kopieren von mehr als einer Ebene bedeutet. Das Problem besteht darin, zu entscheiden, was wir unter einem Level verstehen. Bedenken Sie:
public class Example {
public int foo;
public int[] bar;
public Example() { };
public Example(int foo, int[] bar) { this.foo = foo; this.bar = bar; };
}
Example eg1 = new Example(1, new int[]{1, 2});
Example eg2 = ...
Die normale Interpretation ist, dass eine "flache" Kopie von eg1
ein neues Example
Objekt wäre, dessen foo
Größe 1 entspricht und dessen bar
Feld sich auf dasselbe Array wie im Original bezieht; z.B
Example eg2 = new Example(eg1.foo, eg1.bar);
Die normale Interpretation einer "tiefen" Kopie von eg1
wäre ein neues Example
Objekt, dessen foo
Größe 1 entspricht und dessen bar
Feld sich auf eine Kopie des ursprünglichen Arrays bezieht . z.B
Example eg2 = new Example(eg1.foo, Arrays.copy(eg1.bar));
(Leute mit C / C ++ - Hintergrund könnten sagen, dass eine Referenzzuweisung eine flache Kopie erzeugt. Dies ist jedoch nicht das, was wir normalerweise unter flachem Kopieren im Java-Kontext verstehen ...)
Es gibt zwei weitere Fragen / Unsicherheitsbereiche:
Wie tief ist tief? Hört es auf zwei Ebenen auf? Drei Ebenen? Bedeutet das den gesamten Graphen verbundener Objekte?
Was ist mit gekapselten Datentypen? zB ein String? Ein String ist eigentlich nicht nur ein Objekt. Tatsächlich ist es ein "Objekt" mit einigen Skalarfeldern und einem Verweis auf ein Array von Zeichen. Das Zeichenarray wird jedoch von der API vollständig ausgeblendet. Wenn wir also über das Kopieren eines Strings sprechen, ist es sinnvoll, ihn als "flache" Kopie oder "tiefe" Kopie zu bezeichnen? Oder sollten wir es einfach eine Kopie nennen?
Zum Schluss klonen. Klon ist eine Methode, die für alle Klassen (und Arrays) vorhanden ist und von der allgemein angenommen wird, dass sie eine Kopie des Zielobjekts erzeugt. Jedoch:
Die Spezifikation dieser Methode sagt absichtlich nicht aus, ob es sich um eine flache oder eine tiefe Kopie handelt (vorausgesetzt, dies ist eine sinnvolle Unterscheidung).
Tatsächlich gibt die Spezifikation nicht einmal spezifisch an, dass der Klon ein neues Objekt erzeugt.
Hier ist , was die javadoc sagt:
"Erstellt eine Kopie dieses Objekts und gibt sie zurück. Die genaue Bedeutung von" Kopie "kann von der Klasse des Objekts abhängen. Die allgemeine Absicht ist, dass für jedes Objekt x der Ausdruck
x.clone() != x
wahr ist und dass der Ausdruckx.clone().getClass() == x.getClass()
wahr ist , aber dies sind keine absoluten Anforderungen. Während dies normalerweise der Fall ist,x.clone().equals(x)
ist dies keine absolute Anforderung. "
Beachten Sie, dass dies bedeutet, dass der Klon in einem Extrem möglicherweise das Zielobjekt ist und im anderen Extrem der Klon möglicherweise nicht dem Original entspricht. Und dies setzt voraus, dass der Klon sogar unterstützt wird.
Kurz gesagt, Klon bedeutet möglicherweise für jede Java-Klasse etwas anderes.
Einige Leute argumentieren (wie @supercat in Kommentaren), dass die Java- clone()
Methode kaputt ist. Aber ich denke, die richtige Schlussfolgerung ist, dass das Konzept des Klons im Kontext von OO gebrochen ist. AFAIK, es ist unmöglich, ein einheitliches Klonmodell zu entwickeln, das für alle Objekttypen konsistent und verwendbar ist.
x.clone().equals(x)
der erwartet werden sollte, dass sie wahr ist, erscheint mir seltsam. Die einzige Bedeutung, von der equals
ich mir vorstellen kann, dass sie für alle Objekttypen konsistent ist, ist die Äquivalenz, und keine Instanz eines veränderlichen Typs sollte als einer anderen Instanz gleichwertig angesehen werden. Wenn ein Objekt unveränderlich ist, gibt es keinen Grund, es zu klonen, und wenn ein Objekt veränderlich ist, sollte es nicht seinem Klon entsprechen.
List<T>
ist die Folge der darin enthaltenen Artikelreferenzen; Daher sollte von einem korrekten Klon eines List<T>
Klassentyps T
erwartet werden, dass er auf dieselben Elemente wie die ursprüngliche Liste verweist. Es kann hilfreich sein, eine zu haben EncapsulatedItemList<T>
, die so definiert ist, dass sie den veränderlichen Zustand der darin enthaltenen Elemente einkapselt, und deren Clone
Methode eine Cloner<T>
bei der Konstruktion gelieferte verwendet, um die darin enthaltenen Elemente zu duplizieren.
equals
denke ich, dass Javas großer Fehler darin besteht, dass viele Sammlungen erfordern, dass Typen ihn überschreiben, um etwas anderes als Äquivalenz zu bedeuten. In .net kann der Konstruktor für a ein Dictionary<TKey,TValue>
akzeptieren, IEqualityComparer<TKey>
das etwas viel Lockereres als die Objektäquivalenz verwenden kann, selbst wenn TKey
das Überschreiben von Equals(Object)
nicht der Fall ist. Ich glaube nicht, dass eine solche Funktion in Java existiert. Auf der anderen Seite vertritt .net den Gedanken, dass ==
und .Equals
im Allgemeinen dasselbe funktionieren sollte, ungeachtet der Tatsache, dass ==
keine richtige Äquivalenzbeziehung definiert werden kann.
Der Begriff "Klon" ist nicht eindeutig (obwohl die Java-Klassenbibliothek eine klonbare Schnittstelle enthält) und kann sich auf eine tiefe oder eine flache Kopie beziehen. Tiefe / flache Kopien sind nicht speziell an Java gebunden, sondern ein allgemeines Konzept zum Erstellen einer Kopie eines Objekts und beziehen sich darauf, wie Mitglieder eines Objekts ebenfalls kopiert werden.
Angenommen, Sie haben eine Personenklasse:
class Person {
String name;
List<String> emailAddresses
}
Wie klonen Sie Objekte dieser Klasse? Wenn Sie eine flache Kopie ausführen, können Sie den Namen kopieren und einen Verweis auf emailAddresses
das neue Objekt einfügen. Wenn Sie jedoch den Inhalt der emailAddresses
Liste ändern, ändern Sie die Liste in beiden Kopien (da so Objektreferenzen funktionieren).
Eine tiefe Kopie würde bedeuten, dass Sie jedes Mitglied rekursiv kopieren. Sie müssten also ein neues List
für das neue erstellen Person
und dann den Inhalt vom alten auf das neue Objekt kopieren.
Obwohl das obige Beispiel trivial ist, sind die Unterschiede zwischen tiefen und flachen Kopien erheblich und haben erhebliche Auswirkungen auf jede Anwendung, insbesondere wenn Sie versuchen, im Voraus eine generische Klonmethode zu entwickeln, ohne zu wissen, wie jemand sie später verwenden könnte. Es gibt Zeiten, in denen Sie eine tiefe oder flache Semantik oder einen Hybrid benötigen, in dem Sie einige Mitglieder tief kopieren, andere jedoch nicht.
o.clone() == o
wahr sein kann; siehe meine Antwort.
Die Begriffe "flache Kopie" und "tiefe Kopie" sind etwas vage; Ich würde vorschlagen, die Begriffe "Mitgliedsweiser Klon" und das, was ich als "semantischen Klon" bezeichnen würde, zu verwenden. Ein "memberweiser Klon" eines Objekts ist ein neues Objekt vom gleichen Laufzeittyp wie das Original. Für jedes Feld führt das System effektiv "newObject.field = oldObject.field" aus. Die Basis Object.Clone () führt einen Mitgliedsklon durch. Das klonenweise Klonen von Mitgliedern ist im Allgemeinen der richtige AusgangspunktZum Klonen eines Objekts sind jedoch in den meisten Fällen nach einem Klon auf Mitgliedsbasis einige "Korrekturarbeiten" erforderlich. In vielen Fällen führt der Versuch, ein über memberwise clone erstelltes Objekt zu verwenden, ohne zuvor die erforderliche Korrektur durchzuführen, zu schlechten Ereignissen, einschließlich der Beschädigung des geklonten Objekts und möglicherweise auch anderer Objekte. Einige Leute verwenden den Begriff "flaches Klonen", um sich auf das Klonen von Mitgliedern zu beziehen, aber das ist nicht die einzige Verwendung des Begriffs.
Ein "semantischer Klon" ist ein Objekt, das aus Sicht des Typs dieselben Daten wie das Original enthält . Betrachten Sie zur Prüfung eine BigList, die ein Array> und eine Anzahl enthält. Ein Klon auf semantischer Ebene eines solchen Objekts würde einen Klon auf Mitgliedsebene ausführen, dann das Array> durch ein neues Array ersetzen, neue verschachtelte Arrays erstellen und alle T's von den ursprünglichen Arrays in die neuen kopieren. Es würde keine Art von tiefem Klonen der T's selbst versuchen . Ironischerweise bezeichnen einige Leute das Klonen als "flaches Klonen", während andere es als "tiefes Klonen" bezeichnen. Nicht gerade nützliche Terminologie.
Während es Fälle gibt, in denen ein wirklich tiefes Klonen (rekursives Kopieren aller veränderlichen Typen) nützlich ist, sollte es nur von Typen durchgeführt werden, deren Bestandteile für eine solche Architektur ausgelegt sind. In vielen Fällen ist ein wirklich tiefes Klonen übermäßig und kann Situationen beeinträchtigen, in denen tatsächlich ein Objekt benötigt wird, dessen sichtbarer Inhalt sich auf dieselben Objekte wie ein anderes bezieht (dh eine Kopie auf semantischer Ebene). In Fällen, in denen der sichtbare Inhalt eines Objekts rekursiv von anderen Objekten abgeleitet wird, würde ein Klon auf semantischer Ebene einen rekursiven tiefen Klon implizieren. In Fällen, in denen der sichtbare Inhalt nur ein generischer Typ ist, sollte Code nicht blind alles blind klonen das sieht so aus, als ob es möglicherweise tief klonfähig wäre.