wake-up-neo.com

Wie kann man die Klonmethode richtig überschreiben?

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!

105
Cuga

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, dass Cloneable 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 eine clone-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.

117

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.

53
Aaron Digulla

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);
}
11

Es gibt zwei Fälle, in denen die CloneNotSupportedException ausgelöst wird:

  1. Die Klasse, die geklont wird, implementiert Cloneable nicht (vorausgesetzt, das eigentliche Klonen verschiebt sich letztendlich auf Objects Klonmethode). Wenn die Klasse, in die Sie diese Methode schreiben, in Cloneable implementiert wird, wird dies niemals passieren (da alle Unterklassen sie entsprechend erben).
  2. Die Ausnahme wird explizit von einer Implementierung ausgelöst. Dies ist die empfohlene Methode, um die Klonierbarkeit in einer Unterklasse zu verhindern, wenn die Oberklasse 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.

9
Andrzej Doyle

Verwenden Sie serialization , um tiefe Kopien zu erstellen. Dies ist nicht die schnellste Lösung, hängt jedoch nicht vom Typ ab.

5
Michał Ziober

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);
}
3
Dolph
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) {

                }
        }
    }
}
0
Kvant_hv

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.

0
Francesco Rogo

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.

0
Haggra