Java Objekt clone – shallow vs. deep Beispiel und Alternativen

Java objekte deep vs shallow clone

Clonen von Java Objekten

Die clone() Methode führt eine flache Kopie (shallow copy) eines Objekts durch. Das bedeutet, dass zwar primitive Felder kopiert werden, aber Objekte innerhalb des geklonten Objekts nicht. Vielmehr zeigen die Verweise in dem neuen Objekt auf die Objekte, die auch schon von dem ursprünglichen Objekt referenziert wurden. Dies kann manchmal zu unerwarteten Ergebnissen führen. Deshalb kann es vorkommen, dass eine tiefe Kopie (deep copy) eines Objekts erforderlich ist. Bei dieser zeigen die Referenzen zwar auf Objekte der ursprüngliche Klasse, diese Referenzen zeigen jedoch auf neue Objekte. Somit wurden diese und deren Werte kopiert.

Beispiel

Anhand folgendem Beispiels, indem „Schafe“ geklont werden, wird dieses Problem näher demonstriert.
Dazu wird eine Klasse Sheep erstellt, welche Cloneable (für shallow copy) und Serializable (für deep copy) implementiert und eine Klasse Attribute, welche die Eigenschaften eines Schafes hält.

Am Ende des Artikels können die kompletten Klassen betrachtet werden.

public class Sheep implements Serializable, Cloneable {

    private String name;
    private Attribute attribute;

    //getter, setter, toString, ...

und

public class Attribute implements Serializable {

    private String attributeName;

    //getter, setter, toString, ...

Um eine „deep copy“ durchzuführen wird noch folgende Klasse implementiert:

package at.grid.blog.deep.copy.example;

import java.io.*;

public class ObjectCloner {

    public static Object deepCopy(Object oldObj) throws IOException, ClassNotFoundException {

        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        Object newObject = null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(oldObj);
            oos.flush();
            ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bin);
            newObject = ois.readObject();
        } finally {
            oos.close();
            ois.close();
        }
        return newObject;
    }
}

Nun wird ein neues Schaf-Objekt erstellt und mit einem Attribute-Objekt versehen.

Sheep sheep = new Sheep("Dolly");
sheep.setAttribute(new Attribute("Rot"));

Das Schaf-Objekt wird nun mit der implementierten clone() Methode und mit der selbst implementierten Methode geklont.

Sheep shallowCopy = (Sheep) sheep.clone();
Sheep deepCopy = (Sheep) ObjectCloner.deepCopy(sheep);

Das originale Schaf-Objekt wird modifiziert, indem der Name und die Eigenschaft geändert werden.

sheep.setName("Molly");
sheep.getAttribute().setAttrName("Blau");

Nun lässt man sich die drei Objekte ausgeben:

System.out.println("Original Schaf");
System.out.println("Name: "+sheep.toString());
System.out.println("Eigenschaft: "+sheep.getAttribute().toString());
System.out.println("ShallowCopy");
System.out.println("Name: "+shallowCopy.toString());
System.out.println("Eigenschaft: "+shallowCopy.getAttribute().toString());
System.out.println("DeepCopy");
System.out.println("Name: "+deepCopy.toString());
System.out.println("Eigenschaft: "+deepCopy.getAttribute().toString());

//Ausgabe:
//
//Original Schaf
//Name: Molly
//Eigenschaft: Blau
//ShallowCopy
//Name: Dolly
//Eigenschaft: Blau
//DeepCopy
//Name: Dolly
//Eigenschaft: Rot

Es fällt auf, dass die geklonten Objekte noch den alten Namen haben, bevor dieser beim originalen Objekt geändert wurde. Jedoch hat das Objekt, welches mit der clone() Methode kopiert wurde, auch die Änderung an dem Attribute-Objekt übernommen. Wie Anfangs erwähnt, wurde dieses Objekt nämlich nicht mitgeklont, sondern nur die Referenz auf das Originale gesetzt.

Es empfiehlt sich daher, bei der Verwendung von geklonten Objekten, diese mit Unit-Tests zu überprüfen!

Alternativen

Wer das Rad nicht neu erfinden möchte, kann auch auf die Apache Commons Library zurückgreifen.

Object clonedObj = org.apache.commons.lang.SerializationUtils.clone(origObject);

Diese benötigt jedoch auch ein Objekt, welches Serializable implementiert.

Was tun, wenn die Klasse, welche geklont werden soll, nicht Serializable implementiert und dessen Quellcode nicht geändert werden kann? Die kleine, aber feine Library „cloning“ kann jedes Objekt mit ihrer deepClone() Methode klonen. Sie benutzt dafür Reflection und ist leicht anwendbar, wie der folgende Code verdeutlicht:

Cloner cloner = new Cloner();
Object clonedObj = cloner.deepClone(origObject);

Hier noch das komplette Beispiel:

CloneDemo.java

package at.grid.blog.deep.copy.example;

public class CloneDemo {

    public static void main(String[] args) throws Exception {

        Sheep sheep = new Sheep("Dolly");
        sheep.setAttribute(new Attribute("Rot"));

        Sheep shallowCopy = (Sheep) sheep.clone();
        Sheep deepCopy = (Sheep) ObjectCloner.deepCopy(sheep);

        sheep.setName("Molly");
        sheep.getAttribute().setAttrName("Blau");

        System.out.println("Original Schaf");
        System.out.println("Name: " + sheep.toString());
        System.out.println("Eigenschaft: " + sheep.getAttribute().toString());
        System.out.println("ShallowCopy");
        System.out.println("Name: " + shallowCopy.toString());
        System.out.println("Eigenschaft: " + shallowCopy.getAttribute().toString());
        System.out.println("DeepCopy:");
        System.out.println("Name: " + deepCopy.toString());
        System.out.println("Eigenschaft: " + deepCopy.getAttribute().toString());
    }
}

ObjectCloner.java

package at.grid.blog.deep.copy.example;

import java.io.*;

public class ObjectCloner {

    public static Object deepCopy(Object oldObj) throws IOException, ClassNotFoundException {

        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        Object newObject = null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(oldObj);
            oos.flush();
            ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bin);
            newObject = ois.readObject();
        } finally {
            oos.close();
            ois.close();
        }
        return newObject;
    }
}

Sheep.java

package at.grid.blog.deep.copy.example;

import java.io.Serializable;

public class Sheep implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;
    private String name;

    private Attribute attribute;

    public Sheep(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public String toString() {
        return name;
    }

    public Attribute getAttribute() {
        return attribute;
    }

    public void setAttribute(Attribute atr) {
        this.attribute = atr;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}

Attribute.java

package at.grid.blog.deep.copy.example;

import java.io.Serializable;

public class Attribute implements Serializable {

    private static final long serialVersionUID = 1L;
    private String attributeName;

    public Attribute(String name) {
        this.attributeName = name;
    }

    public void setAttrName(String name) {
        this.attributeName = name;
    }

    public String toString() {
        return attributeName;
    }

}

 

Tagged with:     , , , ,

About the author /


Einst gelernter Werkzeugbautechniker, habe ich vor etlichen Jahren mein Leben der Informatik verschrieben. Zur Zeit studiere ich noch Informatik auf der Fachhochschule Technikum Wien und beschäftige mich am liebsten mit Java, Android und Elektronik.

Related Articles

Post your comments

Your email address will not be published. Required fields are marked *

*

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden .

Unterstütz uns!

Folgt uns!

Diese Seite

wurde erstellt mit Ehrgeiz, Liebe und viel Koffein. Bei der Erstellung kamen keine jar-Dateien zu Schaden. Das Logo wurde erstellt von Star-seven.at.