Ich habe bei der Implementierung von clone () in Java schnell googelt und festgestellt: http://www.javapractices.com/topic/TopicAction.do?Id=71
Es hat den folgenden Kommentar:
Kopierkonstruktoren und statische Factory-Methoden bieten eine Alternative zum Klonen und sind viel einfacher zu implementieren.
Ich möchte nur eine tiefe Kopie erstellen. Die Implementierung von clone () scheint sehr sinnvoll zu sein, aber dieser Artikel mit hohem Google-Rang macht mir ein bisschen Angst.
Hier sind die Probleme, die mir aufgefallen sind:
Kopierkonstruktoren funktionieren nicht mit Generics.
Hier ist ein Pseudocode, der nicht kompiliert werden kann.
public class MyClass<T>{
..
public void copyData(T data){
T copy=new T(data);//This isn't going to work.
}
..
}
Beispiel 1: Verwenden eines Kopierkonstruktors in einer generischen Klasse.
Factory-Methoden haben keine Standardnamen.
Es ist sehr schön, eine Schnittstelle für wiederverwendbaren Code zu haben.
public class MyClass<T>{
..
public void copyData(T data){
T copy=data.clone();//Throws an exception if the input was not cloneable
}
..
}
Beispiel 2: Verwenden von clone () in einer generischen Klasse.
Ich habe festgestellt, dass das Klonen keine statische Methode ist, aber wäre es nicht immer noch notwendig, tiefe Kopien aller geschützten Felder zu erstellen? Bei der Implementierung von clone () erscheint mir der zusätzliche Aufwand, Ausnahmen in nicht klonbaren Unterklassen auszulösen, trivial.
Vermisse ich etwas Alle Einblicke wäre dankbar.
Antworten:
Grundsätzlich ist der Klon kaputt . Mit Generika funktioniert nichts. Wenn Sie so etwas haben (verkürzt, um den Punkt zu vermitteln):
Dann können Sie mit einer Compiler-Warnung die Arbeit erledigen. Da Generika zur Laufzeit gelöscht werden, wird in etwas, das eine Kopie erstellt, eine Compiler-Warnung generiert.
Dies ist in diesem Fall nicht vermeidbar.. Es ist in einigen Fällen vermeidbar (danke, kb304), aber nicht in allen. Stellen Sie sich den Fall vor, in dem Sie eine Unterklasse oder eine unbekannte Klasse unterstützen müssen, die die Schnittstelle implementiert (z. B. wenn Sie eine Sammlung kopierbarer Dateien durchlaufen haben, die nicht unbedingt dieselbe Klasse generiert haben).quelle
Copyable<T>
die Antwort in kd304 viel sinnvoller ist.Es gibt auch das Builder-Muster. Weitere Informationen finden Sie unter Effektives Java.
Ich verstehe Ihre Bewertung nicht. In einem Kopierkonstruktor kennen Sie den Typ genau. Warum müssen Generika verwendet werden?
Vor kurzem gab es hier eine ähnliche Frage .
Ein lauffähiges Beispiel:
quelle
Java hat keine Kopierkonstruktoren im gleichen Sinne wie C ++.
Sie können einen Konstruktor haben, der ein Objekt des gleichen Typs als Argument verwendet, aber nur wenige Klassen unterstützen dies. (weniger als die Anzahl, die das Klonen unterstützen)
Für einen generischen Klon habe ich eine Hilfsmethode, die eine neue Instanz einer Klasse erstellt und die Felder aus dem Original (eine flache Kopie) mithilfe von Reflexionen kopiert (eigentlich so etwas wie Reflexionen, aber schneller).
Für eine tiefe Kopie besteht ein einfacher Ansatz darin, das Objekt zu serialisieren und es zu de-serialisieren.
Übrigens: Mein Vorschlag ist, unveränderliche Objekte zu verwenden, dann müssen Sie sie nicht klonen. ;)
quelle
String
Zum Beispiel ist ein unveränderliches Objekt und Sie können einen String weitergeben, ohne ihn kopieren zu müssen.Ich denke, die Antwort von Yishai könnte verbessert werden, sodass wir mit dem folgenden Code keine Warnung erhalten können:
Auf diese Weise muss eine Klasse, die eine kopierbare Schnittstelle implementieren muss, folgendermaßen aussehen:
quelle
interface Copyable<T extends Copyable>
am ehesten einem Selbsttyp , den Sie in Java codieren können.interface Copyable<T extends Copyable<T>>
, oder? ;)Im Folgenden sind einige Nachteile aufgeführt, die viele Entwickler nicht verwenden
Object.clone()
Object.clone()
method erfordert, dass wir unserem Code viel Syntax hinzufügen, z. B. dieCloneable
Schnittstelle implementieren ,clone()
Methode und Handle definierenCloneNotSupportedException
und schließlichObject.clone()
unser Objekt aufrufen und umwandeln .Cloneable
Schnittstelle fehltclone()
Methode, es ist eine Markierungsschnittstelle und enthält keine Methode, und dennoch müssen wir sie implementieren, um JVM mitzuteilen, dass wirclone()
auf unserem Objekt ausführen können .Object.clone()
ist geschützt, so dass wir unseren eigenenclone()
und indirekten AnrufObject.clone()
von ihm bereitstellen müssen .Object.clone()
kein Konstruktor aufgerufen wird.clone()
Methode in eine untergeordnete Klasse schreiben , z. B. solltenPerson
alle ihre Oberklassen eineclone()
Methode in ihnen definieren oder sie von einer anderen übergeordneten Klasse erben, da sonst diesuper.clone()
Kette fehlschlägt.Object.clone()
unterstützt nur flache Kopien, sodass Referenzfelder unseres neu geklonten Objekts weiterhin Objekte enthalten, die Felder unseres ursprünglichen Objekts enthielten. Um dies zu überwinden, müssen wirclone()
in jeder Klasse, deren Referenz unsere Klasse hält , implementieren und sie dann in unsererclone()
Methode wie im folgenden Beispiel separat klonen .Object.clone()
da die endgültigen Felder nur über Konstruktoren geändert werden können. In unserem Fall erhalten wir, wenn wir möchten, dass jedesPerson
Objekt durch die ID eindeutig ist, das doppelte Objekt, wenn wir es verwenden,Object.clone()
daObject.clone()
der Konstruktor nicht aufgerufen wird und das letzteid
Feld nicht geändert werden kannPerson.clone()
.Kopierkonstruktoren sind besser als
Object.clone()
weil sieLesen Sie mehr über Java Cloning - Copy Constructor versus Cloning
quelle
Normalerweise arbeitet clone () mit einem geschützten Kopierkonstruktor zusammen. Dies geschieht, weil clone () im Gegensatz zu einem Konstruktor virtuell sein kann.
In einem Klassenkörper für Abgeleitet von einer Superklassenbasis haben wir
Im einfachsten Fall würden Sie also einen virtuellen Kopierkonstruktor mit dem clone () hinzufügen. (In C ++ empfiehlt Joshi das Klonen als Konstruktor für virtuelle Kopien.)
Es wird komplizierter, wenn Sie super.clone () wie empfohlen aufrufen möchten und diese Mitglieder zur Klasse hinzufügen müssen. Sie können dies versuchen
Nun, wenn eine Hinrichtung, haben Sie
und du hast es dann getan
Dies würde clone () in der abgeleiteten Klasse (die oben genannte) aufrufen. Dies würde super.clone () aufrufen - was möglicherweise implementiert ist oder nicht, aber es wird empfohlen, es aufzurufen. Die Implementierung übergibt dann die Ausgabe von super.clone () an einen Konstruktor für geschützte Kopien, der eine Basis verwendet, und Sie übergeben alle endgültigen Mitglieder an diese.
Dieser Kopierkonstruktor ruft dann den Kopierkonstruktor der Superklasse auf (wenn Sie wissen, dass er einen hat) und legt das Finale fest.
Wenn Sie zur clone () -Methode zurückkehren, legen Sie alle nicht endgültigen Mitglieder fest.
Kluge Leser werden feststellen, dass ein Kopierkonstruktor in der Basis von super.clone () aufgerufen wird - und erneut aufgerufen wird, wenn Sie den Superkonstruktor im geschützten Konstruktor aufrufen, sodass Sie möglicherweise den aufrufen Super Copy-Konstruktor zweimal. Wenn es Ressourcen sperrt, wird es das hoffentlich wissen.
quelle
Ein Muster, das für Sie möglicherweise funktioniert, ist das Kopieren auf Bean-Ebene. Grundsätzlich verwenden Sie einen Konstruktor ohne Argumente und rufen verschiedene Setter auf, um die Daten bereitzustellen. Sie können sogar die verschiedenen Bean-Eigenschaftsbibliotheken verwenden, um die Eigenschaften relativ einfach festzulegen. Dies ist nicht dasselbe wie ein Klon (), aber für viele praktische Zwecke ist es in Ordnung.
quelle
Die klonbare Schnittstelle ist in dem Sinne defekt, dass sie nutzlos ist, aber gut funktioniert und zu einer besseren Leistung für große Objekte führen kann - 8 Felder und mehr, aber dann wird die Escape-Analyse fehlschlagen. Daher ist es vorzuziehen, die meiste Zeit den Kopierkonstruktor zu verwenden. Die Verwendung von Klon für Array ist schneller als Arrays.copyOf, da die Länge garantiert gleich ist.
Weitere Details finden Sie hier https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html
quelle
Wenn man sich nicht aller Macken zu 100% bewusst ist
clone()
, würde ich raten, sich davon fernzuhalten. Ich würde das nichtclone()
kaputt sagen . Ich würde sagen: Verwenden Sie es nur, wenn Sie ganz sicher sind, dass es Ihre beste Option ist. Ein Kopierkonstruktor (oder eine Factory-Methode, die meiner Meinung nach nicht wirklich wichtig ist) ist einfach zu schreiben (möglicherweise langwierig, aber einfach). Er kopiert nur das, was Sie kopieren möchten, und kopiert die Art und Weise, wie die Dinge kopiert werden sollen. Sie können es genau auf Ihre Bedürfnisse zuschneiden.Plus: Es ist einfach zu debuggen, was passiert, wenn Sie Ihre Kopierkonstruktor- / Factory-Methode aufrufen.
Und erstellt
clone()
nicht sofort eine "tiefe" Kopie Ihres Objekts, vorausgesetzt, Sie meinen, dass nicht nur die Verweise (z. B. auf aCollection
) kopiert werden. Aber lesen Sie hier mehr über tief und flach: Tiefe Kopie, flache Kopie, Klonquelle
Was Sie vermissen, ist, dass der Klon standardmäßig flache Kopien und Konventionen erstellt und dass es im Allgemeinen nicht möglich ist, tiefe Kopien zu erstellen.
Das Problem ist, dass Sie keine tiefen Kopien von zyklischen Objektdiagrammen erstellen können, ohne verfolgen zu können, welche Objekte besucht wurden. clone () bietet kein solches Tracking (da dies ein Parameter für .clone () sein müsste) und erstellt daher nur flache Kopien.
Selbst wenn Ihr eigenes Objekt .clone für alle seine Mitglieder aufruft, ist es keine tiefe Kopie.
quelle
SuperDuperList<T>
oder abgeleitetes davon hat, sollte das Klonen eine neue Instanz des gleichen Typs ergeben wie die, die geklont wurde; Es sollte vom Original getrennt sein, sich jedochT
in derselben Reihenfolge wie das Original auf dasselbe beziehen . Nichts, was von diesem Punkt an an einer der beiden Listen getan wird, sollte die Identität der in der anderen gespeicherten Objekte beeinflussen. Ich kenne keine nützliche "Konvention", die eine generische Sammlung erfordern würde, um ein anderes Verhalten aufzuweisen.