Geklonter Kaffee ist kalt

Eine unabhängige Kopie eines Objekts zu erhalten ist keine triviale Angelegenheit. Die zentralen Ansätze in C++, C# und Java, diese Funktionalität zu realisieren, sind die Methoden „Clone“ (C#) bzw. „clone“ (Java) und copy-Konstruktoren. Der folgende Beitrag konzentriert sich auf die „clone“-Methode in Java.

Das „clone“-Konzept in Java besteht aus drei verschiedenen Komponenten: dem Interface „java.lang.Cloneable“, der Exception „java.lang.CloneNotSupportedException“ und der Methode „clone“ in „java.lang.Object“. Die Methode „clone“ ist als „protected“ deklariert und vermutlich nicht in Java selbst implementiert, sondern „native“. Sie kopiert alle Felder des Objekts in ein neues Objekt. Es wird kein Konstruktor für das neue Objekt aufgerufen, und die einzelnen Felder werden nicht rekursiv kopiert, sondern einfach zugewiesen. Die Methode überprüft, ob das aktuelle Objekt das (leere) Marker-Interface „java.lang.Cloneable“ implementiert. Wenn nicht, wird die Exception ausgelöst. Eine Klasse, die das Interface implementiert, sollte die Methode mit einer „public“-Methode überschreiben und „super.clone()“ aufrufen.

Soviel zur trockenen Beschreibung. Jetzt schauen wir mal, warum das Konzept „broken by design“ ist: Das Interface „java.lang.Cloneable“ deklariert keine „clone“-Methode, erst recht keine öffentliche. Das heißt ganz einfach, dass folgender Code nicht kompiliert:

public class MyUsefulClass {
  public static Cloneable[] cloneMyArray(Cloneable[] myArray) {
    int len = myArray.length;
    Cloneable[] buf = new Cloneable[len];
    for (int i = 0 ; i < len ; ++i) {
      buf[i] = myArray[i].clone();
    }
    return buf;
  }
}

Nur Klassen und Interfaces, die ausdrücklich eine öffentliche „clone“-Methode deklarieren, können in solchen Ausdrücken stehen.

Soweit so schrecklich, aber es geht noch weiter. Java verwendet teilweise „Checked exceptions“, das heißt wenn eine Methode ausgeführt wird, die eine Exception auslösen kann, muss die aufrufende Methode diese behandeln oder als die Exception auslösend deklariert sein, so dass die nächsthöhere Ebene sich darum kümmern muss. Ein durchaus interessantes Konzept mit Vor- und Nachteilen, im konkreten Fall aber eine ziemliche Katastrophe. Da die „clone“-Methode in „java.lang.Object“ als „throws java.lang.CloneNotSupportedException“ deklariert ist, müssen alle sie aufrufenden Methoden (also z. B. alle „clone“-Methoden in abgeleiteten Klassen) diese Exception behandeln oder sie durchreichen. Da „java.lang.Object.clone“ aber nur dann diese Exception auslöst, wenn das aktuelle Objekt nicht „java.lang.Cloneable“ implementiert, kann sich jede „clone“-Methode in einer Klasse, die direkt oder indirekt das Interface implementiert ziemlich sicher sein, dass sie mit dieser Exception nichts zu tun haben wird.

Selbstverständlich gibt es auch davon eine Ausnahme, und damit sind wir auch schon beim nächsten großen Design-Problem: Ein einmal in einer Klasse implementiertes Interface klebt an jeder Unterklasse wie ein Kaugummi im Teppich. Wenn eine Klasse also partout nicht geklont werden möchte, obwohl eine ihrer Superklassen „java.lang.Cloneable“ implementiert, muss sie eine eigene „clone“-Methode implementieren und in dieser eine Exception werfen.

Ja, und dann gibt es noch ein paar unschöne Kleinigkeiten: „clone“ gibt immer eine „java.lang.Object“-Referenz zurück, die erst mal gecastet werden muss. Außerdem muss die Methode auf Fähigkeiten der Virtuellen Maschine zurückgreifen, die eine Implementierung in Java sehr schwer bis unmöglich machen. Und schließlich funktioniert klonen nicht wirklich gut mit „final“-Feldern.

Also noch mal zusammengefasst: Das Clone-Design in Java ermöglicht es, dass eine Klasse „java.lang.Cloneable“ implementiert aber keine „clone“-Methode hat und zwingt Klassen, die wissen, dass keine Exception auftreten kann zu einer Exception-Behandlung.

Links

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.