Kopieren von Objekten
Willemers Informatik-Ecke

Eine Zuweisung ist keine Kopie

Wird in Java eine Variable vom Typ einer Klasse gebildet, handelt es sich um eine Referenz, die auf ein Objekt verweist und nicht um das Objekt selbst. Das wird besonders deutlich, wenn man versucht, eine Kopie eines Objekts durch einfaches Zuweisen dieser Variablen zu erzeugen. Betrachten wir zunächst eine einfache Klasse A, die ein Attribut a besitzt.
class A {
   int a = 1;
}
Nun wird ein Objekt von A angelegt und einer anderen Referenz auf A zugewiesen:
A a1 = new A();
A a2 = a1; // Kopie?
a2.a = 4;
System.out.println(a1.a); // gibt 4 statt 1
Tatsächlich entsteht keine Kopie des Objekts, sondern eine Kopie der Referenz! Die beiden Referenzen a1 und a2 sind Kopien. Sie zeigen beide auf das gleiche Objekt. Darum kann über a2 eine Änderung ausgeführt werden, die bei Zugriff über a1 wirksam ist.

Der Vorteil der Verwendung von Referenzen liegt darin, dass Objekte große Datenmengen enthalten können. Eine Kopie, die beispielsweise auch bei einer Parameterübergabe vorliegt, führt dann zu großen Datenbewegungen. Wird nur die Referenz kopiert, werden nur wenige Bytes bewegt.

Eigene Kopiermethode

Die naheliegende Konsequenz ist, dass wir eine Methode implementieren, die eine Kopie des Objekts anfertigt. Dazu legt sie ein Objekt an und kopiert alle Attribute in das neue Objekt.
public class A {
    int a = 1;
    public A kopiere() {
        A k = new A();
        k.a = this.a; // Achtung: nichts vergessen!
        return k;
    }
Nun wird eine Kopie über die Methode kopiere aufgerufen.
A a1 = new A();
A a2 = a1.kopiere();
a2.a = 4;
System.out.println(a1.a); // gibt 1 aus
Der Nachteil ist, dass es keinen Standardnamen gibt, sondern jede Klasse ihre eigene Idee implementieren kann. Das erschwert dann das Erweitern.

Das klassische clone

In der Klasse Object, die Basisklasse aller Klassen ist, ist die Methode clone vorgesehen, um eine Kopie anzufertigen.

Zu diesem Zweck implementiert eine Klasse das Interface Cloneable. Allerdings schreibt Clonable keineswegs die Implementierung der Methode clone vor. Da clone bereits in der Klasse Object definiert ist, können Sie diese aufrufen und wenn Sie die Methode der Basisklasse aufrufen, kopiert diese auch die flachen Attribute der Klasse. Allerdings haben Sie dann noch die Exception CloneNotSupportedException am Hals. Hinzu kommt, dass Sie sich aufgrund des Rückgabetyps Object auch noch einen hässlichen Cast einfangen.

public class A implements Cloneable {
    int a = 1;
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
Wieder kopieren wir a2 von a1, dieses Mal mit clone.
A a1 = new A();
try {
    A a2 = (A) a1.clone();
    a2.a = 4;
    System.out.println(a1.a); // gibt 1 aus
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
Die Ausführung von clone auf der Object-Basis sorgt für eine flache Kopie. Alle Attribute werden kopiert. Sind einige der Attribute allerdings Referenzen, tritt das Problem erneut auf, weil diese nur kopiert werden, also keine rekursive Kopie stattfindet.

Aus diesem Grund müssen Referenz-Attribute - auch Arrays - jeweils mit clone explizit kopiert werden, um eine &aquot;tiefe&aquot; Kopie zu bekommen.

Ein eigene Implementierung von clone ist allerdings inzwischen in die Kritik geraten. Dazu gehört beispielsweise, dass Clonable die Implementierung von clone nicht erzwingt. Damit ist eine funktionierende tiefe Kopie nicht gewährleistet.

Copy-Konstruktor

In C++ gibt es einen Copy-Konstruktor. Dieser sorgt dafür, dass eine Kopie ausgeführt wird, sobald eine Kopie über das Objekt ausgeführt wird. Das gelingt, weil C++ zwischen Objekt und Zeiger unterscheidet, während Java nur mit Referenzen arbeitet.

Prinzipiell lässt sich in Java auch ein Konstruktor erstellen, der als Parameter ein Objekt erlaubt. Leider wird er nicht, wie in C++, automatisch eingebunden.

class A {
   public A() { }
   public A(A q) {
       this.a = q.a;
   }
   int a = 1;
}
Nun muss die Kopie durch Aufruf erfolgen.
A a1 = new A();
A a2 = new A(a1);
a2.a = 4;
System.out.println(a1.a); // gibt 1 aus
clone() vs copy constructor vs factory method?