Tiefe Objektkopien (Deep Copy)

Die clone() Methode liefert nur flache Kopien eines Objektes. Mit Hilfe der Serialisierung kommt man schnell auch zu tiefen Kopien. Klassen können die clone() Methode von Object überschreiben und so eine Kopie der Werte liefern. Die Standardimplementierung ist jedoch so angelegt, dass diese Kopie flach ist, was bedeutet, Referenzen auf Objekte die von dem zu klonenden Objekt ausgehen, werden beibehalten und diese Objekte nicht extra kopiert. Als Beispiel kann die einfache Datenstruktur eines Feldes genügen, welches auf Vector Objekte verweiset. Eine Klon dieses Feldes ist lediglich ein zweites Feld, dessen Elemente auf die gleichen Vektoren zeigen. Eine Änderung wird also beiden Felder bewusst.

Möchten wir das Verhalten ändern und eine tiefe Kopie anfertigen, so haben wir mit einem kleinen Trick keine Mühe damit. Die Idee ist, dass wir das zu klonende Objekt einfach Serialisieren und dann wieder auspacken. Die zu klonenden Objekte müssen dann nur das Serializable Interface implementieren.

public static Object deepCopy( Object o ) throws Exception
{
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  new ObjectOutputStream( baos ).writeObject( o );

  ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() );

  return new ObjectInputStream(bais).readObject();
}

Das einzige was wir zum Gelingen der Methode deepCopy() machen müssen, ist das Objekt in ein Bytefeld zu serialisieren und dann wieder auszulesen und zu einem Objekt konvertieren. Den Einsatz eines ByteArrayOutputStream haben wir schon gesehen, als wir die Länge eines Objektes herausfinden wollten. Nur fügen wir nun das Feld wieder einem ByteArrayInputStream zu, aus dessen Daten dann der ObjectInputStream wieder das Objekt rekreieren kann.

Überzeugen wir uns an Hand eines kleines Programms, dass die tiefe Kopie tatsächlich etwas anderes als ein clone() ist. (Verwendet werden Raw-Types um das Bsp. kurz zu halten.)

public static void main( String args[] ) throws Exception
{
  Map map = new HashMap() {{
    put( "Cul de Paris", "hinten unter dem Kleid getragenes Gestell oder Polster" );
  }};

  LinkedList l1 = new LinkedList(); 
  l1.add( map );

  List l2 = (List) l1.clone();

  List l3 = (List) deepCopy( l1 );

  map.clear();

  System.out.println( l1 );
  System.out.println( l2 );
  System.out.println( l3 );
}

Zunächst erstellen wir eine Map, die wie anschießend in eine Liste packen. Die Map enthält ein Pärchen. Klonen wir mit clone() die Liste, so wird zwar die Liste selbst kopiert, aber nicht die Map. Die tiefe Kopie kopiert neben der Liste auch gleich die Map mit. Das sehen wir dann, wenn wir den Eintrag aus dem Map löschen. Dann ergibt l1 genauso wie l2 eine leere Liste, da l2 zur die Verweise auf die Map gespeichert hat, die dann aber geleert ist. Anders ist dies bei l3, der tiefen Kopie; hier ist das Paar noch vorhanden. Die Ausgabe ist dann:

[{}]
[{}]
[{Cul de Paris=hinten unter dem Kleid getragenes Gestell...}]

An diesem Beispiel sehen wir, wie wunderbar die Stream-Klassen zusammenarbeiten. Einzige Voraussetzung zum Gelingen ist die Implementierung der Schnittstelle Serializable. Da aber die zu klonenden Klassen auch clone() implementieren müssen, gilt in der Regel, dass sie serialisierbar sind. Daher steht in der implements Zeile die Schnittstelle Clonable und Serializable direkt nebeneinander.

Ähnliche Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert