Tiefe Kopie, flache Kopie, Klon

73

Ich muss die Unterschiede zwischen Deep Copy, Flat Copy und Clone in Java klären

trs
quelle
4
Klingt sowohl nach einer Hausaufgabenfrage als auch nach etwas, über das Tausende von Artikeln online sind. Wie dies .
Wahrhaftigkeit
Können Sie etwas genauer sein? Gibt es eine Reihe von Methoden oder Bibliotheken, die Sie für ein bestimmtes Problem verwenden möchten?
Dan

Antworten:

110

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 newexplizit 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 eg1ein neues ExampleObjekt wäre, dessen fooGröße 1 entspricht und dessen barFeld 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 eg1wäre ein neues ExampleObjekt, dessen fooGröße 1 entspricht und dessen barFeld 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() != xwahr ist und dass der Ausdruck x.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.

Stephen C.
quelle
2
Die Vorstellung, von x.clone().equals(x)der erwartet werden sollte, dass sie wahr ist, erscheint mir seltsam. Die einzige Bedeutung, von der equalsich 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.
Supercat
1
@supercat - das ist logisch. Trotzdem sind einige Leute von dieser Tatsache überrascht. Und beachte das Zitat aus dem Javadoc !!
Stephen C
IMHO sollten einige der frühen Entwurfsentscheidungen und Empfehlungen in Java und .net als "Fehler" betrachtet werden. Der Umgang mit identischen Feldern, die die Objektidentität einschließen, und solchen, die den veränderlichen Objektzustand einschließen, vereinfacht die Laufzeit, aber das Fehlen jeglicher vom Menschen analysierbarer Konventionen zur Unterscheidung verschiedener Feldtypen führt zu erheblichem Wirrwarr. Objekte sollten nur eine Art von
Klonmethode verfügbar machen
... kann diejenigen klonen oder nicht, die weder einkapseln (im Allgemeinen wäre das Klonen solcher Elemente ineffizient, aber nicht falsch). Der Zustand von a List<T>ist die Folge der darin enthaltenen Artikelreferenzen; Daher sollte von einem korrekten Klon eines List<T>Klassentyps Terwartet 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 CloneMethode eine Cloner<T>bei der Konstruktion gelieferte verwendet, um die darin enthaltenen Elemente zu duplizieren.
Supercat
In Bezug auf equalsdenke 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 TKeydas Ü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 .Equalsim Allgemeinen dasselbe funktionieren sollte, ungeachtet der Tatsache, dass ==keine richtige Äquivalenzbeziehung definiert werden kann.
Supercat
22

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 emailAddressesdas neue Objekt einfügen. Wenn Sie jedoch den Inhalt der emailAddressesListe ä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 Listfür das neue erstellen Personund 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.

Adam Batkin
quelle
1
+1 für eine nette Antwort, aber was ist mit diesem "Verweis auf emailAddresses"? weil ich fühle, dass emailAddresses selbst Referenz ist.
JAVA
Es ist wie "Verweis auf Verweis", wenn man "Verweis auf E-Mail-Adressen" sagt, was nichts ist. Ich habe verstanden, was Sie sagen wollen, aber es kann nur wenige Menschen verwirren. Und unsere Antworten sollten auch nicht wenige Menschen stören :)
JAVA
18
  • Deep Copy: Klonen Sie dieses Objekt und jeden Verweis auf jedes andere Objekt, das es hat
  • Flache Kopie: Klonen Sie dieses Objekt und behalten Sie seine Referenzen bei
  • Object clone () löst CloneNotSupportedException aus: Es wird nicht angegeben, ob dies eine tiefe oder flache Kopie zurückgeben soll, aber zumindest: o.clone ()! = O.
Sam Pullara
quelle
4
wirklich o.clone() == owahr sein kann; siehe meine Antwort.
Stephen C
2

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.

Superkatze
quelle