Es ist etwas schwierig, eine Deep Object Copy-Funktion zu implementieren. Welche Schritte unternehmen Sie, um sicherzustellen, dass das ursprüngliche und das geklonte Objekt keinen Bezug haben?
Es ist etwas schwierig, eine Deep Object Copy-Funktion zu implementieren. Welche Schritte unternehmen Sie, um sicherzustellen, dass das ursprüngliche und das geklonte Objekt keinen Bezug haben?
Antworten:
Ein sicherer Weg besteht darin, das Objekt zu serialisieren und dann zu deserialisieren. Dies stellt sicher, dass alles eine brandneue Referenz ist.
Hier ist ein Artikel darüber, wie dies effizient durchgeführt werden kann.
Vorsichtsmaßnahmen: Klassen können die Serialisierung überschreiben, sodass keine neuen Instanzen erstellt werden, z. B. für Singletons. Auch dies funktioniert natürlich nicht, wenn Ihre Klassen nicht serialisierbar sind.
Einige Leute haben die Verwendung oder Überschreibung erwähnt Object.clone()
. Tu es nicht. Object.clone()
hat einige große Probleme, und von seiner Verwendung wird in den meisten Fällen abgeraten. Eine vollständige Antwort finden Sie unter Punkt 11 aus " Effective Java " von Joshua Bloch. Ich glaube, Sie können sicher Object.clone()
auf Arrays vom primitiven Typ verwenden, aber abgesehen davon müssen Sie vorsichtig sein, wenn Sie den Klon richtig verwenden und überschreiben.
Die Schemata, die auf Serialisierung (XML oder auf andere Weise) beruhen, sind klobig.
Hier gibt es keine einfache Antwort. Wenn Sie ein Objekt tief kopieren möchten, müssen Sie das Objektdiagramm durchlaufen und jedes untergeordnete Objekt explizit über den Kopierkonstruktor des Objekts oder eine statische Factory-Methode kopieren, die wiederum das untergeordnete Objekt tief kopiert. Unveränderliche (z. B. String
) müssen nicht kopiert werden. Nebenbei sollten Sie aus diesem Grund die Unveränderlichkeit bevorzugen.
Sie können eine tiefe Kopie mit Serialisierung erstellen, ohne Dateien zu erstellen.
Ihr Objekt, das Sie tief kopieren möchten, muss implement serializable
. Wenn die Klasse nicht endgültig ist oder nicht geändert werden kann, erweitern Sie die Klasse und implementieren Sie serialisierbar.
Konvertieren Sie Ihre Klasse in einen Stream von Bytes:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();
Stellen Sie Ihre Klasse aus einem Bytestrom wieder her:
ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();
instance
in diesem Fall nicht serialisierbar ?
Sie können org.apache.commons.lang3.SerializationUtils.clone(T)
in Apache Commons Lang einen auf Serialisierung basierenden Deep Clone erstellen, aber seien Sie vorsichtig - die Leistung ist miserabel.
Im Allgemeinen empfiehlt es sich, für jede Klasse eines Objekts im zu klonenden Objektdiagramm eigene Klonmethoden zu schreiben.
org.apache.commons.lang.SerializationUtils
Eine Möglichkeit, Deep Copy zu implementieren, besteht darin, jeder zugeordneten Klasse Kopierkonstruktoren hinzuzufügen. Ein Kopierkonstruktor verwendet eine Instanz von 'this' als einzelnes Argument und kopiert alle Werte daraus. Ziemlich viel Arbeit, aber ziemlich unkompliziert und sicher.
BEARBEITEN: Beachten Sie, dass Sie zum Lesen von Feldern keine Zugriffsmethoden verwenden müssen. Sie können direkt auf alle Felder zugreifen, da die Quellinstanz immer vom gleichen Typ ist wie die Instanz mit dem Kopierkonstruktor. Offensichtlich, könnte aber übersehen werden.
Beispiel:
public class Order {
private long number;
public Order() {
}
/**
* Copy constructor
*/
public Order(Order source) {
number = source.number;
}
}
public class Customer {
private String name;
private List<Order> orders = new ArrayList<Order>();
public Customer() {
}
/**
* Copy constructor
*/
public Customer(Customer source) {
name = source.name;
for (Order sourceOrder : source.orders) {
orders.add(new Order(sourceOrder));
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Bearbeiten: Beachten Sie, dass Sie bei Verwendung von Kopierkonstruktoren den Laufzeittyp des zu kopierenden Objekts kennen müssen. Mit dem obigen Ansatz können Sie eine gemischte Liste nicht einfach kopieren (möglicherweise können Sie dies mit einem Reflexionscode tun).
Toyota
, wird Ihr Code ein Car
in die Zielliste einfügen . Für ein ordnungsgemäßes Klonen muss die Klasse im Allgemeinen eine virtuelle Factory-Methode bereitstellen, deren Vertrag besagt, dass sie ein neues Objekt ihrer eigenen Klasse zurückgibt. Der Kopierkonstruktor selbst sollte protected
sicherstellen, dass er nur zum Erstellen von Objekten verwendet wird, deren genauer Typ mit dem des zu kopierenden Objekts übereinstimmt.
Sie können eine Bibliothek verwenden , die über eine einfache API verfügt und ein relativ schnelles Klonen mit Reflektion durchführt (sollte schneller sein als Serialisierungsmethoden).
Cloner cloner = new Cloner();
MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o
Apache Commons bietet eine schnelle Möglichkeit, ein Objekt tief zu klonen.
My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);
XStream ist in solchen Fällen sehr nützlich. Hier ist ein einfacher Code zum Klonen
private static final XStream XSTREAM = new XStream();
...
Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));
Ein sehr einfacher und einfacher Ansatz besteht darin, Jackson JSON zu verwenden, um komplexe Java-Objekte in JSON zu serialisieren und zurückzulesen.
Für Spring Framework- Benutzer. Klasse verwenden org.springframework.util.SerializationUtils
:
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}
Für komplizierte Objekte und wenn die Leistung nicht signifikant ist, verwende ich eine JSON-Bibliothek wie 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 transient
Felder nicht kopiert werden und Objekte mit Zirkelverweis mit Ursache StackOverflowError
.
public static <T> T copy(T anObject, Class<T> classInfo) {
Gson gson = new GsonBuilder().create();
String text = gson.toJson(anObject);
T newObject = gson.fromJson(text, classInfo);
return newObject;
}
public static void main(String[] args) {
String originalObject = "hello";
String copiedObject = copy(originalObject, String.class);
}
Verwenden Sie XStream ( http://x-stream.github.io/ ). Sie können sogar steuern, welche Eigenschaften Sie durch Anmerkungen ignorieren oder den Eigenschaftsnamen explizit für die XStream-Klasse angeben können. Darüber hinaus müssen Sie keine klonbare Schnittstelle implementieren.
Tiefes Kopieren kann nur mit Zustimmung jeder Klasse durchgeführt werden. Wenn Sie die Kontrolle über die Klassenhierarchie haben, können Sie die klonbare Schnittstelle und die Clone-Methode implementieren. Andernfalls ist eine sichere Kopie nicht sicher durchzuführen, da das Objekt möglicherweise auch Nicht-Datenressourcen (z. B. Datenbankverbindungen) gemeinsam nutzt. Im Allgemeinen wird jedoch tiefes Kopieren in der Java-Umgebung als schlechte Praxis angesehen und sollte über die entsprechenden Entwurfspraktiken vermieden werden.
import com.thoughtworks.xstream.XStream;
public class deepCopy {
private static XStream xstream = new XStream();
//serialize with Xstream them deserialize ...
public static Object deepCopy(Object obj){
return xstream.fromXML(xstream.toXML(obj));
}
}
BeanUtils macht einen wirklich guten Job beim tiefen Klonen von Bohnen.
BeanUtils.cloneBean(obj);
1)
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;
}
}
2)
// (1) create a MyPerson object named Al
MyAddress address = new MyAddress("Vishrantwadi ", "Pune", "India");
MyPerson al = new MyPerson("Al", "Arun", address);
// (2) make a deep clone of Al
MyPerson neighbor = (MyPerson)deepClone(al);
Hier müssen Ihre MyPerson- und MyAddress-Klassen eine serilazierbare Schnittstelle implementieren
Verwenden von Jackson zum Serialisieren und Deserialisieren des Objekts. Für diese Implementierung muss das Objekt die Serializable-Klasse nicht implementieren.
<T> T clone(T object, Class<T> clazzType) throws IOException {
final ObjectMapper objMapper = new ObjectMapper();
String jsonStr= objMapper.writeValueAsString(object);
return objMapper.readValue(jsonStr, clazzType);
}