Ich muss einen tiefen Klon in einem meiner Objekte implementieren, der keine Superklasse hat.
Wie gehe ich am besten mit der überprüften CloneNotSupportedException
um, die von der Superklasse (die Object
ist) geworfen wird?
Ein Mitarbeiter hat mir empfohlen, folgendermaßen vorzugehen:
@Override
public MyObject clone()
{
MyObject foo;
try
{
foo = (MyObject) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new Error();
}
// Deep clone member fields here
return foo;
}
Dies scheint mir eine gute Lösung zu sein, aber ich wollte es der StackOverflow-Community mitteilen, um zu sehen, ob es andere Erkenntnisse gibt, die ich einbringen kann. Vielen Dank!
Müssen Sie unbedingt clone
verwenden? Die meisten Leute sind sich einig, dass Javas clone
gebrochen ist.
Josh Bloch über Design - Copy Constructor versus Klonen
Wenn Sie den Artikel über das Klonen in meinem Buch gelesen haben, insbesondere wenn Sie zwischen den Zeilen lesen, werden Sie wissen, dass
clone
zutiefst gebrochen ist. [...] Schade, dassCloneable
kaputt ist, aber es passiert.
Weitere Diskussionen zu diesem Thema finden Sie in seinem Buch Effektive Java 2nd Edition, Element 11: Überschreibe clone
sinnvoll. Er empfiehlt, stattdessen einen Kopierkonstruktor oder eine Kopierfabrik zu verwenden.
Er fuhr fort, Seiten mit Seiten darüber zu schreiben, wie, wenn Sie das für nötig halten, clone
implementieren sollte. Aber er schloss damit:
Sind all diese Komplexitäten wirklich notwendig? Selten. Wenn Sie eine Klasse erweitern, die
Cloneable
implementiert, haben Sie kaum eine andere Wahl, als eineclone
-Methode mit gutem Verhalten zu implementieren. Andernfalls ist es besser, alternative Mittel zum Kopieren von Objekten bereitzustellen oder die Fähigkeit einfach nicht bereitzustellen.
Der Schwerpunkt lag bei ihm, nicht bei mir.
Da Sie deutlich gemacht haben, dass Sie keine andere Wahl haben als clone
zu implementieren, können Sie in diesem Fall Folgendes tun: Stellen Sie sicher, dass MyObject extends Java.lang.Object implements Java.lang.Cloneable
. Wenn dies der Fall ist, können Sie garantieren, dass Sie NIEMALS eine CloneNotSupportedException
abfangen. AssertionError
zu werfen, wie einige vorgeschlagen haben, erscheint sinnvoll, aber Sie können auch einen Kommentar hinzufügen, der erklärt, warum der catch-Block niemals in diesem speziellen Fall eingegeben wird.
Alternativ, wie auch andere vorgeschlagen haben, können Sie vielleicht clone
implementieren, ohne super.clone
aufzurufen.
Manchmal ist es einfacher einen Kopierkonstruktor zu implementieren:
public MyObject (MyObject toClone) {
}
Es erspart Ihnen den Umgang mit CloneNotSupportedException
, arbeitet mit final
-Feldern und Sie müssen sich nicht um den zurückzugebenden Typ kümmern.
Die Art und Weise, wie Ihr Code funktioniert, kommt der "kanonischen" Schreibweise ziemlich nahe. Ich würde jedoch eine AssertionError
in den Fang werfen. Es signalisiert, dass diese Linie niemals erreicht werden sollte.
catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
Es gibt zwei Fälle, in denen die CloneNotSupportedException
ausgelöst wird:
Cloneable
nicht (vorausgesetzt, das eigentliche Klonen verschiebt sich letztendlich auf Object
s Klonmethode). Wenn die Klasse, in die Sie diese Methode schreiben, in Cloneable
implementiert wird, wird dies niemals passieren (da alle Unterklassen sie entsprechend erben).Cloneable
ist.Der letztere Fall kann in Ihrer Klasse nicht vorkommen (da Sie die Methode der Superklasse direkt im try
-Block aufrufen, auch wenn sie von einer Unterklasse aufgerufen wird, die super.clone()
aufruft) und die erstere sollte nicht, da Ihre Klasse Cloneable
eindeutig implementieren sollte.
Grundsätzlich sollten Sie den Fehler mit Sicherheit protokollieren. In diesem speziellen Fall wird dies jedoch nur passieren, wenn Sie die Definition Ihrer Klasse durcheinander bringen. Behandeln Sie es daher wie eine geprüfte Version von NullPointerException
(oder ähnlich) - es wird niemals geworfen, wenn Ihr Code funktionsfähig ist.
In anderen Situationen müssten Sie auf diese Eventualität vorbereitet sein. Es gibt keine Garantie dafür, dass ein bestimmtes Objekt is klonbar ist. Wenn Sie die Ausnahme abfangen, sollten Sie je nach dieser Bedingung geeignete Maßnahmen ergreifen (fahren Sie mit dem vorhandenen Objekt fort , verwenden Sie eine alternative Klonierungsstrategie, z. B. serialize-deserialize, werfen Sie eine IllegalParameterException
, wenn für Ihre Methode der Parameter durch Klonierung erforderlich ist usw. usw.).
Edit : Obwohl ich generell darauf hinweisen möchte, dass clone()
wirklich schwierig ist, richtig zu implementieren, und für Anrufer schwierig zu wissen, ob der Rückgabewert so ist, wie sie es wollen, doppelt, wenn Sie tiefe vs. flache Klone in Betracht ziehen. Es ist oft besser, das Ganze komplett zu vermeiden und einen anderen Mechanismus zu verwenden.
Verwenden Sie serialization , um tiefe Kopien zu erstellen. Dies ist nicht die schnellste Lösung, hängt jedoch nicht vom Typ ab.
Sie können geschützte Kopierkonstruktoren wie folgt implementieren:
/* This is a protected copy constructor for exclusive use by .clone() */
protected MyObject(MyObject that) {
this.myFirstMember = that.getMyFirstMember(); //To clone primitive data
this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects
// etc
}
public MyObject clone() {
return new MyObject(this);
}
public class MyObject implements Cloneable, Serializable{
@Override
@SuppressWarnings(value = "unchecked")
protected MyObject clone(){
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream bOs = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bOs);
oos.writeObject(this);
ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
return (MyObject)ois.readObject();
} catch (Exception e) {
//Some seriouse error :< //
return null;
}finally {
if (oos != null)
try {
oos.close();
} catch (IOException e) {
}
if (ois != null)
try {
ois.close();
} catch (IOException e) {
}
}
}
}
Nur weil Javas Implementierung von Cloneable defekt ist, heißt das nicht, dass Sie keine eigene erstellen können.
Wenn der eigentliche Zweck von OP darin bestand, einen tiefen Klon zu erstellen, denke ich, dass es möglich ist, eine Schnittstelle wie diese zu erstellen:
public interface Cloneable<T> {
public T getClone();
}
verwenden Sie dann den zuvor erwähnten Prototypkonstruktor, um ihn zu implementieren:
public class AClass implements Cloneable<AClass> {
private int value;
public AClass(int value) {
this.vaue = value;
}
protected AClass(AClass p) {
this(p.getValue());
}
public int getValue() {
return value;
}
public AClass getClone() {
return new AClass(this);
}
}
und eine andere Klasse mit einem AClass-Objektfeld:
public class BClass implements Cloneable<BClass> {
private int value;
private AClass a;
public BClass(int value, AClass a) {
this.value = value;
this.a = a;
}
protected BClass(BClass p) {
this(p.getValue(), p.getA().getClone());
}
public int getValue() {
return value;
}
public AClass getA() {
return a;
}
public BClass getClone() {
return new BClass(this);
}
}
Auf diese Weise können Sie ein Objekt der Klasse BClass problemlos tiefklonen, ohne @SuppressWarnings oder anderen gimmicky Code zu benötigen.
Obwohl die meisten Antworten hier gültig sind, muss ich sagen, dass Ihre Lösung auch so ist, wie die eigentlichen Java-API-Entwickler dies tun. (Entweder Josh Bloch oder Neal Gafter)
Hier ist ein Auszug aus der Klasse openJDK, ArrayList:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
Wie Sie bemerkt haben und andere erwähnt haben, hat CloneNotSupportedException
fast keine Chance, geworfen zu werden, wenn Sie erklärt haben, dass Sie die Cloneable
-Schnittstelle implementieren.
Auch Sie müssen die Methode nicht überschreiben, wenn Sie in der überschriebenen Methode nichts Neues tun. Sie müssen es nur überschreiben, wenn Sie zusätzliche Operationen an dem Objekt ausführen oder es öffentlich machen müssen.
Letztendlich ist es immer noch am besten, es zu vermeiden und es auf andere Weise zu tun.