Java: Empfohlene Lösung zum tiefen Klonen / Kopieren einer Instanz

176

Ich frage mich, ob es eine empfohlene Methode gibt, um Deep Instance in Java zu klonen / zu kopieren.

Ich habe 3 Lösungen im Sinn, aber ich kann einige verpassen, und ich würde gerne Ihre Meinung haben

Bearbeiten: Bohzo-Vorschlag einschließen und Frage verfeinern: Es geht mehr um tiefes Klonen als um flaches Klonen.

Mach es selbst:

Codieren Sie die Eigenschaften des Klons von Hand nach den Eigenschaften und überprüfen Sie, ob auch veränderbare Instanzen geklont werden.
pro:
- Kontrolle darüber, was ausgeführt wird
- schnelle Ausführung
Nachteile:
- mühsam zu schreiben und zu warten
- fehleranfällig (Fehler beim Kopieren / Einfügen, fehlende Eigenschaft, neu zugewiesene veränderbare Eigenschaft)

Verwenden Sie Reflexion:

Mit Ihren eigenen Reflexionswerkzeugen oder mit einem externen Helfer (wie Jakarta Common-Beans) ist es einfach, eine generische Kopiermethode zu schreiben, die die Arbeit in einer Zeile erledigt.
Pro:
- Einfach zu schreiben
- Keine Wartungsprobleme
:
- Weniger Kontrolle darüber, was passiert
- Fehler bei veränderlichen Objekten, wenn das Reflection-Tool nicht auch Unterobjekte klont
- Langsamere Ausführung

Verwenden Sie das Klon-Framework:

Verwenden Sie ein Framework, das dies für Sie erledigt, z. B .:
Commons-lang SerializationUtils
Java Deep Cloning Library
Dozer
Kryo

pro:
- genau wie Reflexion
- mehr Kontrolle darüber, was genau geklont werden soll.
Nachteile:
- Jede veränderbare Instanz wird auch am Ende der Hierarchie vollständig geklont. Die
Ausführung kann sehr langsam sein

Verwenden Sie die Bytecode-Instrumentierung, um zur Laufzeit einen Klon zu schreiben

javassit , BCEL oder cglib können verwendet werden, um einen dedizierten Kloner so schnell wie eine geschriebene Hand zu generieren. Kennt jemand eine Bibliothek, die eines dieser Tools für diesen Zweck verwendet?

Was habe ich hier vermisst?
Welches würdest du empfehlen?

Vielen Dank.

Guillaume
quelle
1
Anscheinend ist die Java Deep Cloning Library hierher gezogen: code.google.com/p/cloning
Mr_and_Mrs_D

Antworten:

155

Für tiefes Klonen (klont die gesamte Objekthierarchie):

  • commons-lang SerializationUtils - mithilfe der Serialisierung - wenn alle Klassen unter Ihrer Kontrolle stehen und Sie die Implementierung erzwingen können Serializable.

  • Java Deep Cloning Library - mithilfe von Reflection - in Fällen, in denen die Klassen oder Objekte, die Sie klonen möchten, außerhalb Ihrer Kontrolle liegen (eine Bibliothek eines Drittanbieters) und Sie sie nicht implementieren Serializablekönnen oder in Fällen, die Sie nicht implementieren möchten Serializable.

Für flaches Klonen (klont nur die Eigenschaften der ersten Ebene):

Ich habe bewusst auf die Option "Do-it-yourself" verzichtet. Die oben genannten APIs bieten eine gute Kontrolle darüber, was geklont werden soll und was nicht (z. B. mit transientoder String[] ignoreProperties), sodass eine Neuerfindung des Rads nicht bevorzugt wird.

Bozho
quelle
Danke Bozho, das ist wertvoll. Und ich stimme Ihnen in Bezug auf die DIY-Option zu! Haben Sie jemals die Commons-Serialisierung und / oder die Deep Cloning Lib ausprobiert? Was ist mit den Perfs?
Guillaume
Ja, ich habe aus den oben genannten Gründen alle oben genannten Optionen verwendet :) Nur die Klonbibliothek hatte einige Probleme, wenn CGLIB-Proxys beteiligt waren, und einige gewünschte Funktionen fehlten, aber ich denke, das sollte jetzt behoben werden.
Bozho
Hey, wenn meine Entität angehängt ist und ich faule Dinge habe, überprüft SerializationUtils die Datenbank auf die faulen Eigenschaften? Denn das ist was ich will und das tut es nicht!
Cosmin Cosmin
Wenn Sie eine aktive Sitzung haben - ja, das tut es.
Bozho
@Bozho Sie meinen also, wenn alle Objekte in der Bean serialisierbar sind, wird org.apache.commons.beanutils.BeanUtils.cloneBean (obj) eine tiefe Kopie erstellen?
Hop
36

Joshua Blochs Buch enthält ein ganzes Kapitel mit dem Titel "Punkt 10: Klon mit Bedacht überschreiben", in dem er erklärt, warum das Überschreiben von Klonen größtenteils eine schlechte Idee ist, da die Java-Spezifikation dafür viele Probleme verursacht.

Er bietet einige Alternativen:

  • Verwenden Sie anstelle eines Konstruktors ein Factory-Muster:

         public static Yum newInstance(Yum yum);
  • Verwenden Sie einen Kopierkonstruktor:

         public Yum(Yum yum);

Alle Auflistungsklassen in Java unterstützen den Kopierkonstruktor (z. B. new ArrayList (l);)

LeWoody
quelle
1
Einverstanden. In meinem Projekt habe ich eine CopyableSchnittstelle definiert , die eine getCopy()Methode enthält . Verwenden Sie das Prototypmuster einfach manuell.
Gpampara
Nun, ich habe nicht nach der klonbaren Oberfläche gefragt, sondern nach der Durchführung eines Deep Clone / Copy-Vorgangs. Mit einem Konstruktor oder einer Factory müssen Sie Ihre neue Instanz noch aus Ihrer Quelle erstellen.
Guillaume
@ Guillaume Ich denke, Sie müssen vorsichtig sein, wenn Sie die Wörter Deep Clone / Copy verwenden. Klonen und Kopieren in Java bedeuten NICHT dasselbe. Die Java-Spezifikation hat mehr zu sagen ... Ich denke, Sie möchten eine tiefe Kopie von dem, was ich sagen kann.
LeWoody
OK Java-Spezifikation ist genau, was ein Klon ist ... Aber wir können auch von dem Klon in einer allgemeineren Bedeutung sprechen ... Zum Beispiel heißt eine der von bohzo empfohlenen Bibliotheken 'Java Deep Cloning Library' ...
Guillaume
2
@LWoodyiii diese newInstance()Methode und der YumKonstruktor würden tiefe oder flache Kopien machen?
Geek
9

Seit Version 2.07 unterstützt Kryo das flache / tiefe Klonen :

Kryo kryo = new Kryo();
SomeClass someObject = ...
SomeClass copy1 = kryo.copy(someObject);
SomeClass copy2 = kryo.copyShallow(someObject);

Kryo ist schnell, auf und von ihrer Seite finden Sie möglicherweise eine Liste der Unternehmen, die es in der Produktion verwenden.

Andrey Chaschev
quelle
5

Verwenden Sie XStream toXML / fromXML im Speicher. Extrem schnell und gibt es schon lange und es geht stark. Objekte müssen nicht serialisierbar sein und Sie müssen keine Reflektion verwenden (obwohl XStream dies tut). XStream kann Variablen erkennen, die auf dasselbe Objekt verweisen, und nicht versehentlich zwei vollständige Kopien der Instanz erstellen. Viele solche Details wurden im Laufe der Jahre herausgearbeitet. Ich habe es für eine Reihe von Jahren verwendet und es ist ein go to. Es ist ungefähr so ​​einfach zu bedienen, wie Sie sich vorstellen können.

new XStream().toXML(myObj)

oder

new XStream().fromXML(myXML)

Klonen,

new XStream().fromXML(new XStream().toXML(myObj))

Genauer gesagt:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));
Ranx
quelle
3

Für komplizierte Objekte und wenn die Leistung nicht signifikant ist, verwende ich gson, um das Objekt in json-Text zu serialisieren, und deserialisiere dann den Text, um ein neues Objekt zu erhalten.

gson, das auf Reflexion basiert, funktioniert in den meisten Fällen, außer dass transientFelder nicht kopiert werden und Objekte mit Zirkelverweis mit Ursache StackOverflowError.

public static <ObjectType> ObjectType Copy(ObjectType AnObject, Class<ObjectType> ClassInfo)
{
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(AnObject);
    ObjectType newObject = gson.fromJson(text, ClassInfo);
    return newObject;
}
public static void main(String[] args)
{
    MyObject anObject ...
    MyObject copyObject = Copy(o, MyObject.class);

}
Tiboo
quelle
2

Hängt davon ab.

Verwenden Sie für Geschwindigkeit DIY. Verwenden Sie zum Schutz vor Kugeln die Reflexion.

Übrigens ist die Serialisierung nicht dasselbe wie refl, da einige Objekte möglicherweise überschriebene Serialisierungsmethoden (readObject / writeObject) bereitstellen und fehlerhaft sein können

Yoni Roit
quelle
1
Reflexion ist nicht kugelsicher: Sie kann in Situationen führen, in denen Ihr geklontes Objekt auf Ihre Quelle verweist ... Wenn sich die Quelle ändert, ändert sich auch der Klon!
Guillaume
1

Ich würde den DIY-Weg empfehlen, der in Kombination mit einer guten hashCode () - und equals () -Methode in einem Unit-Test leicht zu beweisen sein sollte.

Dominik Sandjaja
quelle
Nun, das faule Ich schimpft viel, wenn man so einen Dummy-Code erstellt. Aber es sieht aus wie der klügere Weg ...
Guillaume
2
Entschuldigung, aber DIY ist der Weg NUR, wenn keine andere Lösung für Sie geeignet ist .. was fast nie ist
Bozho
1

Ich würde vorschlagen, Object.clone () zu überschreiben, zuerst super.clone () aufzurufen und dann ref = ref.clone () für alle Referenzen aufzurufen, die Sie tief kopieren möchten. Es ist mehr oder weniger Do-it-yourself- Ansatz, benötigt aber etwas weniger Codierung.

x4u
quelle
2
Das ist eines der vielen Probleme der (kaputten) Klonmethode: In einer Klassenhierarchie muss man immer super.clone () aufrufen, was leicht vergessen werden kann. Deshalb würde ich lieber einen Kopierkonstruktor verwenden.
Hilfsmethode
0

Implementieren Sie für das Deep Cloning Serializable für jede Klasse, die Sie so klonen möchten

public static class Obj implements Serializable {
    public int a, b;
    public Obj(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

Und dann nutzen Sie diese Funktion:

public static Object deepClone(Object object) {
    try {
        ByteArrayOutputStream baOs = new ByteArrayOutputStream();
        ObjectOutputStream oOs = new ObjectOutputStream(baOs);
        oOs.writeObject(object);
        ByteArrayInputStream baIs = new ByteArrayInputStream(baOs.toByteArray());
        ObjectInputStream oIs = new ObjectInputStream(baIs);
        return oIs.readObject();
    }
    catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

so was: Obj newObject = (Obj)deepClone(oldObject);

Alexander Maslew
quelle