Über Java klonbar

95

Ich habe nach Tutorials gesucht, die Java erklären Cloneable, aber keine guten Links erhalten, und Stack Overflow wird sowieso immer offensichtlicher.

Ich würde gerne folgendes wissen:

  1. Cloneablebedeutet, dass wir einen Klon oder eine Kopie von Objekten haben können, indem wir die CloneableSchnittstelle implementieren . Was sind die Vor- und Nachteile davon?
  2. Wie erfolgt das rekursive Klonen, wenn das Objekt ein zusammengesetztes Objekt ist?
Tagträumer
quelle
2
Die Vor- und Nachteile im Vergleich zu was?
Galactus
4
Ich habe das gelesen, um den Vorteil zu bedeuten, dass eine Klasse klonbar ist oder nicht. Ich
bin

Antworten:

159

Das erste, was Sie wissen sollten, Cloneableist - verwenden Sie es nicht.

Es ist sehr schwer, das Klonen mit CloneableRecht zu implementieren , und der Aufwand lohnt sich nicht.

Verwenden Sie stattdessen einige andere Optionen, wie Apache-Commons SerializationUtils(Deep-Clone) oder BeanUtils(Flach-Clone), oder verwenden Sie einfach einen Kopierkonstruktor.

Hier finden Sie die Ansichten von Josh Bloch zum Klonen mit Cloneable, was die vielen Nachteile des Ansatzes erklärt. ( Joshua Bloch war ein Sun-Mitarbeiter und leitete die Entwicklung zahlreicher Java-Funktionen.)

Bozho
quelle
1
Ich habe Blochs Worte verlinkt (anstatt sie zu zitieren)
Bozho
3
Beachten Sie, dass Block sagt, dass Cloneable nicht verwendet werden soll. Er sagt nicht, dass er kein Klonen verwenden soll (oder zumindest hoffe ich nicht). Es gibt viele Möglichkeiten, das Klonen einfach zu implementieren, die weitaus effizienter sind als Klassen wie SerializationUtils oder BeanUtils, die Reflektion verwenden. Ein Beispiel finden Sie in meinem Beitrag unten.
Charles
Was ist die Alternative zu einem Kopierkonstruktor beim Definieren von Schnittstellen? einfach eine Kopiermethode hinzufügen?
Benez
@benez würde ich ja sagen. seit java-8 können sie staticmethoden in schnittstellen haben, also einfach ein static WhatEverTheInterface copy(WhatEverTheInterface initial)? Aber ich frage mich, was Ihnen das bringt, da Sie beim Klonen Felder von einem Objekt kopieren, aber eine Schnittstelle nur Methoden definiert. Möchtest du das erklären?
Eugene
40

Cloneable selbst ist leider nur eine Marker-Schnittstelle, das heißt: Es definiert nicht die clone () -Methode.

Dadurch wird das Verhalten der geschützten Object.clone () -Methode geändert, wodurch eine CloneNotSupportedException für Klassen ausgelöst wird, die Cloneable nicht implementieren, und für Klassen, die dies tun, eine flache Kopie in Bezug auf die Mitglieder durchgeführt wird.

Auch wenn dies das Verhalten ist, nach dem Sie suchen, müssen Sie Ihre eigene clone () -Methode implementieren, um es öffentlich zu machen.

Wenn Sie Ihren eigenen clone () implementieren, sollten Sie mit dem von super.clone () erstellten Objekt beginnen, das garantiert zur richtigen Klasse gehört, und dann eine zusätzliche Population von Feldern ausführen, falls eine flache Kopie nicht der Fall ist Sie wollen. Das Aufrufen eines Konstruktors von clone () wäre problematisch, da dies die Vererbung unterbrechen würde, falls eine Unterklasse ihre eigene zusätzliche klonbare Logik hinzufügen möchte. Wenn es super.clone () aufrufen würde, würde es in diesem Fall ein Objekt der falschen Klasse erhalten.

Dieser Ansatz umgeht jedoch jede Logik, die in Ihren Konstruktoren definiert ist und möglicherweise problematisch sein kann.

Ein weiteres Problem besteht darin, dass alle Unterklassen, die vergessen haben, clone () zu überschreiben, automatisch die flache Standardkopie erben, was im Falle eines veränderlichen Status (der jetzt zwischen der Quelle und der Kopie geteilt wird) wahrscheinlich nicht das ist, was Sie wollen.

Die meisten Entwickler verwenden Cloneable aus diesen Gründen nicht und implementieren stattdessen einfach einen Kopierkonstruktor.

Für weitere Informationen und mögliche Fallstricke von Cloneable empfehle ich das Buch Effective Java von Joshua Bloch

Luke Hutteman
quelle
12
  1. Das Klonen ruft eine außersprachliche Art der Konstruktion von Objekten auf - ohne Konstruktoren.
  2. Das Klonen erfordert, dass Sie irgendwie mit CloneNotSupportedException behandeln - oder Client-Code für die Behandlung belästigen.
  3. Die Vorteile sind gering - Sie müssen lediglich keinen Kopierkonstruktor manuell schreiben.

Verwenden Sie Cloneable also mit Bedacht. Es bietet Ihnen nicht genügend Vorteile im Vergleich zu dem Aufwand, den Sie aufwenden müssen, um alles richtig zu machen.

Vladimir Ivanov
quelle
Wie Bozho sagte, benutze Cloneable nicht. Verwenden Sie stattdessen einen Kopierkonstruktor. javapractices.com/topic/TopicAction.do?Id=12
Bane
@Bane, was ist, wenn Sie den Typ des zu klonenden Objekts nicht kennen, wie würden Sie wissen, welchen Kopierkonstruktor der Klasse Sie aufrufen sollen?
Steve Kuo
@Steve: Ich folge nicht. Wenn Sie ein Objekt klonen möchten, wissen Sie vermutlich bereits, um welchen Typ es sich handelt. Schließlich haben Sie das Objekt in der Hand, das Sie klonen möchten. Und wenn es eine Situation gibt, in der Ihr Objekt seinen spezifischen Typ durch einen allgemeineren verloren hat, können Sie es dann nicht mit einer einfachen 'Instanz von' bewerten ???
Bane
4
@Bane: Angenommen, Sie haben eine Liste von Objekten, die alle vom Typ A abgeleitet sind, möglicherweise mit 10 verschiedenen Typen. Sie wissen nicht, welchen Typ jedes Objekt hat. Die Verwendung von instanceof ist in diesem Fall eine SEHR schlechte Idee. Wenn Sie einen anderen Typ hinzufügen, müssen Sie jedes Mal eine weitere Testinstanz hinzufügen. Und was ist, wenn sich die abgeleiteten Klassen in einem anderen Paket befinden, auf das Sie nicht einmal zugreifen können? Klonen ist ein häufiges Muster. Ja, die Java-Implementierung ist schlecht, aber es gibt viele Möglichkeiten, die gut funktionieren. Ein Kopierkonstruktor ist keine äquivalente Operation.
Charles
@ Charles: In Ermangelung eines detaillierten Beispiels und mangelnder Erfahrung im Umgang mit dieser Art von Problem muss ich mich an Bloch wenden. Punkt # 11. Es ist lang und ein bisschen schwer zu lesen, aber es heißt im Grunde: "Vermeiden Sie Klonen, wann immer Sie können, Kopierkonstruktoren sind Ihr Freund".
Bane
7

Das Klonen ist ein grundlegendes Programmierparadigma. Die Tatsache, dass Java es in vielerlei Hinsicht schlecht implementiert hat, verringert keineswegs die Notwendigkeit des Klonens. Und es ist einfach, das Klonen zu implementieren, das funktioniert, wie Sie möchten, flach, tief, gemischt, was auch immer. Sie können sogar den Namensklon für die Funktion verwenden und Cloneable nicht implementieren, wenn Sie möchten.

Angenommen, ich habe Klassen A, B und C, wobei B und C von A abgeleitet sind. Wenn ich eine Liste von Objekten vom Typ A wie folgt habe:

ArrayList<A> list1;

Diese Liste kann nun Objekte vom Typ A, B oder C enthalten. Sie wissen nicht, um welchen Typ es sich bei den Objekten handelt. Sie können die Liste also nicht wie folgt kopieren:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

Wenn das Objekt tatsächlich vom Typ B oder C ist, erhalten Sie nicht die richtige Kopie. Und was ist, wenn A abstrakt ist? Nun haben einige Leute dies vorgeschlagen:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

Das ist eine sehr, sehr schlechte Idee. Was ist, wenn Sie einen neuen abgeleiteten Typ hinzufügen? Was ist, wenn B oder C in einem anderen Paket enthalten sind und Sie in dieser Klasse keinen Zugriff darauf haben?

Was Sie tun möchten, ist Folgendes:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

Viele Leute haben angegeben, warum die grundlegende Java-Implementierung von Klonen problematisch ist. Aber es ist leicht so zu überwinden:

In Klasse A:

public A clone() {
    return new A(this);
}

In Klasse B:

@Override
public B clone() {
    return new B(this);
}

In Klasse C:

@Override
public C clone() {
    return new C(this):
}

Ich implementiere Cloneable nicht, sondern verwende nur denselben Funktionsnamen. Wenn Ihnen das nicht gefällt, nennen Sie es etwas anderes.

Charles
quelle
Ich habe dies gerade gesehen, nachdem ich auf Ihren Kommentar in einer separaten Antwort geantwortet hatte. Ich sehe, wohin Sie jetzt gehen, jedoch zwei Dinge: 1) Das OP hat speziell nach der Verwendung von Cloneable gefragt (nicht nach dem generischen Konzept des Klonens), und 2) Sie spalten hier ein wenig die Haare, um zwischen einem Kopierkonstruktor zu unterscheiden und das generische Konzept des Klonens. Die Idee, die Sie hier vermitteln, ist gültig, aber an der Wurzel verwenden Sie nur einen Kopierkonstruktor. ;)
Bane
Obwohl ich sagen möchte, dass ich Ihrem Ansatz hier zustimme, der darin besteht, eine A # copyMethod () einzuschließen, anstatt den Benutzer zu zwingen, den Kopierkonstruktor direkt aufzurufen.
Bane
5

A) Es gibt nicht viele Vorteile des Klons gegenüber einem Kopierkonstruktor. Das wahrscheinlich größte ist die Möglichkeit, ein neues Objekt mit genau demselben dynamischen Typ zu erstellen (vorausgesetzt, der deklarierte Typ ist klonbar und verfügt über eine öffentliche Klonmethode).

B) Der Standardklon erstellt eine flache Kopie und bleibt eine flache Kopie, es sei denn, Ihre Klonimplementierung ändert dies. Dies kann schwierig sein, insbesondere wenn Ihre Klasse Abschlussfelder hat

Bozho hat recht, Klon kann schwierig sein, richtig zu machen. Ein Kopierkonstruktor / eine Kopierfabrik erfüllt die meisten Anforderungen.

ILMTitan
quelle
0

Was sind die Nachteile von Cloneable?

Das Klonen ist sehr gefährlich, wenn das Objekt, das Sie kopieren, eine Komposition aufweist. In diesem Fall müssen Sie über mögliche Nebenwirkungen nachdenken, da das Klonen eine flache Kopie erstellt:

Angenommen, Sie haben ein Objekt für die Manipulation im Zusammenhang mit der Datenbank. Angenommen, dieses Objekt hat ein ConnectionObjekt als eine der Eigenschaften.

Wenn also jemand einen Klon originalObjecterstellt, sagen wir, das Objekt, das erstellt wird cloneObject. Hier die originalObjectund cloneObjecthalten die gleiche Referenz für ConnectionObjekt.

Angenommen, originalObjectdas ConnectionObjekt wird geschlossen, sodass der cloneObjectWille jetzt nicht mehr funktioniert, da das connectionObjekt zwischen ihnen geteilt und von der tatsächlich geschlossen wurde originalObject.

Ein ähnliches Problem kann auftreten, wenn Sie beispielsweise ein Objekt klonen möchten, dessen Eigenschaft IOStream ist.

Wie erfolgt das rekursive Klonen, wenn das Objekt ein zusammengesetztes Objekt ist?

Cloneable führt eine flache Kopie durch. Dies bedeutet, dass Daten des Originalobjekts und des Klonobjekts auf dieselbe Referenz / denselben Speicher verweisen. Im Gegensatz dazu werden im Fall einer tiefen Kopie Daten aus dem Speicher des ursprünglichen Objekts in den Speicher des Klonobjekts kopiert.

027
quelle
Ihr letzter Absatz ist sehr verwirrt. Cloneableführt keine Kopie durch, Object.clonetut es. "Daten aus dem Speicher des Originalobjekts werden in den Speicher des Klonobjekts kopiert" ist genau das, was Object.clonefunktioniert. Sie müssen über den Speicher von referenzierten Objekten sprechen, um das tiefe Kopieren zu beschreiben.
Aioobe