Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 9 - OOP III: Interfaces

## 9.4 Weitere Anwendungen von Interfaces

- 9.4 Weitere Anwendungen von Interfaces
  - 9.4.1 Konstanten in Interfaces
  - 9.4.2 Implementierung von Flags
  - 9.4.3 Nachbildung von Funktionszeigern

### 9.4.1 Konstanten in Interfaces

Neben abstrakten Methoden können Interfaces auch Konstanten, also Membervariablen mit den Attributen `static` und `final`, enthalten. Wenn eine Klasse ein Interface implementiert, erbt es auch alle Konstanten. Es ist auch erlaubt, daß ein Interface ausschließlich Konstanten enthält.

Dieses Feature kann zum Beispiel nützlich sein, wenn ein Programm sehr viele Konstanten definiert. Anstatt sie in ihren eigenen Klassen zu belassen und bei jedem Gebrauch mit `Klasse.Name` aufzurufen, könnte ein einzelnes Interface definiert werden, das alle Konstantendefinitionen vereinigt. Wenn nun jede Klasse, in der diese Konstanten benötigt werden, dieses Interface implementiert, stehen alle darin definierten Konstanten direkt zur Verfügung und können ohne vorangestellten Klassennamen aufgerufen werden.

Das folgende Listing zeigt die Anwendung dieses Prinzips am Beispiel eines Interfaces, das eine Reihe von Debug-Konstanten zur Verfügung stellt. Sie werden beispielsweise zur bedingten Übersetzung oder Steuerung von Debug-Ausgaben verwendet:

    001 /* Listing0911.java */
    002
    003 interface Debug
    004 {
    005   public static final boolean FUNCTIONALITY1 = false;
    006   public static final boolean FUNCTIONALITY2 = true;
    007   public static final boolean FUNCTIONALITY3 = false;
    008   public static final boolean FUNCTIONALITY4 = false;
    009 }
    010
    011 public class Listing0911
    012 implements Debug
    013 {
    014   public static void main(String[] args)
    015   {
    016     //...
    017     if (FUNCTIONALITY1) {
    018       System.out.println("...");
    019     }
    020     //...
    021     if (FUNCTIONALITY2) {
    022       System.out.println("...");
    023     }
    024     //...
    025   }
    026 }
| Listing0911.java

Listing 9.11: Konstanten in Interfaces

### 9.4.2 Implementierung von Flags

Einige Interfaces definieren weder Methoden noch Konstanten. Sie stellen statt dessen eine Art Flag, also einen logischen Schalter dar, der zur Compile- und Laufzeit abgefragt werden kann. Beispiele aus der Klassenbibliothek sind die Interfaces `java.io.Serializable` oder `java.lang.Cloneable`. Während `Serializable` in Kapitel 41 ausführlich gewürdigt wird, wollen wir uns nachfolgend die Bedeutung des Interfaces `Cloneable` ansehen.

`Cloneable` ist ein Schalter für die in der Klasse `Object` implementierte Methode `clone`. Implementiert eine abgeleitete Klasse dieses Interface nicht, so deutet `clone` das als fehlende Fähigkeit (oder Bereitschaft) der Klasse, eine Objektkopie herzustellen, und löst beim Aufruf eine `CloneNotSupportedException` aus. Implementiert die abgeleitete Klasse dagegen `Cloneable`, erzeugt ein Aufruf von `clone` eine elementweise Kopie des aktuellen Objekts.

Es ist wichtig zu verstehen, was der Begriff elementweise bedeutet - insbesondere wenn die Klasse Objekte als Membervariablen enthält. Beim Aufruf von `clone` werden nämlich lediglich die Verweise auf diese Membervariablen kopiert, nicht aber die dahinter stehenden Objekte (bei primitiven Membervariablen macht das keinen Unterschied, denn sie werden nicht als Zeiger gespeichert). Diese Vorgehensweise wird auch als shallow copy bezeichnet ("flache Kopie").

Soll eine deep copy ("tiefe Kopie") angelegt werden, muß man `clone` überlagern und selbst dafür sorgen, daß alle vorhandenen Objekt-Membervariablen kopiert werden. Da jedes Memberobjekt weitere Objekte enthalten kann, die kopiert werden müssen, ist das Erstellen einer tiefen Kopie in der Regel ein rekursiver Vorgang.

Wir wollen uns beispielhaft das tiefe Kopieren der folgenden Klasse `BinTreeNode` ansehen. Diese stellt einen Knoten in einem Binärbaum dar und besitzt einen Namen und die üblichen Verweise auf den linken und rechten Unterbaum, ebenfalls vom Typ `BinTreeNode`. Beim Kopieren wird das aktuelle Objekt durch Aufruf von `super.clone` zunächst flach kopiert. Dann wird `clone` rekursiv aufgerufen, um Kopien des linken bzw. rechten Unterbaums anzulegen. Enthält ein Unterbaum den Wert `null`, so terminiert der Kopiervorgang und mit ihm die Rekursion. Auf diese Weise wird der Knoten mit allen Unterknoten, Unterunterknoten usw. kopiert, und es entsteht eine Kopie, deren Objektvariablen keine gemeinsamen Objekte mit dem Original mehr besitzen.

    001 /* BinTreeNode.java */
    002
    003 class BinTreeNode
    004 implements Cloneable
    005 {
    006   String      name;
    007   BinTreeNode leftChild;
    008   BinTreeNode rightChild;
    009
    010   public BinTreeNode(String name)
    011   {
    012     this.name       = name;
    013     this.leftChild  = null;
    014     this.rightChild = null;
    015   }
    016
    017   public Object clone()
    018   {
    019     try {
    020       BinTreeNode newNode = (BinTreeNode)super.clone();
    021       if (this.leftChild != null) {
    022         newNode.leftChild = (BinTreeNode)this.leftChild.clone();
    023       }
    024       if (this.rightChild != null) {
    025         newNode.rightChild = (BinTreeNode)this.rightChild.clone();
    026       }
    027       return newNode;
    028     } catch (CloneNotSupportedException e) {
    029       //Kann eigentlich nicht auftreten...
    030       throw new InternalError();
    031     }
    032   }
    033 }
| BinTreeNode.java

Listing 9.12: Implementierung einer tiefen Kopie

### 9.4.3 Nachbildung von Funktionszeigern

Eine wichtige Anwendung von Interfaces besteht darin, die aus C oder C++ bekannten, aber in Java nicht vorhandenen Funktionszeiger nachzubilden, die es ermöglichen, eine Funktion als Argument an andere Funktionen zu übergeben. Nützlich ist das vor allem, wenn die Konfigurationsanforderungen einer Funktion die durch die Übergabe von Variablen gegebenen Möglichkeiten übersteigen. Beispiele für ihre Anwendung sind etwa Funktionsplotter oder Callback-Funktionen bei der Programmierung grafischer Oberflächen.

Funktionszeiger können leicht mit Hilfe von Interfaces nachgebildet werden. Dazu wird zunächst ein Interface definiert, das eine einzelne Methode `f` des gewünschten Typs deklariert. Es kann dann von unterschiedlichen Klassen so implementiert werden, daß in `f` jeweils die gewünschte Berechnung ausgeführt wird. Anstelle der Übergabe eines Zeigers wird nun einfach ein Objekt dieser Klasse instanziert und an die zu konfigurierende Methode übergeben. Diese wird vom Typ des Interfaces deklariert und kann so die Methode `f` aufrufen.

Als Beispiel soll ein Programm geschrieben werden, das in der Lage ist, eine Wertetabelle für beliebige `double`-Funktionen auszugeben. Wir definieren dazu zunächst ein Interface `DoubleMethod`, das eine Methode `compute` deklariert, die zu einem `double`-Argument ein `double`-Ergebnis berechnet.

    001 /* DoubleMethod.java */
    002
    003 public interface DoubleMethod
    004 {
    005   public double compute(double value);
    006 }
| DoubleMethod.java

Listing 9.13: Das Interface DoubleMethod

Anschließend implementieren wir das Interface in verschiedenen Klassen und stellen die Funktionen Exponentation, Quadratwurzel, Multiplikation mit zwei und Quadrat zur Verfügung. Anschließend instanzieren wir diese Klassen und übergeben die Objekte nacheinander an die Methode `printTable`, mit der die Wertetabelle erzeugt und ausgegeben wird:

    001 /* Listing0914.java */
    002
    003 class MathExp
    004 implements DoubleMethod
    005 {
    006   public double compute(double value)
    007   {
    008     return Math.exp(value);
    009   }
    010 }
    011
    012 class MathSqrt
    013 implements DoubleMethod
    014 {
    015   public double compute(double value)
    016   {
    017     return Math.sqrt(value);
    018   }
    019 }
    020
    021 class Times2
    022 implements DoubleMethod
    023 {
    024   public double compute(double value)
    025   {
    026     return 2 * value;
    027   }
    028 }
    029
    030 class Sqr
    031 implements DoubleMethod
    032 {
    033   public double compute(double value)
    034   {
    035     return value * value;
    036   }
    037 }
    038
    039 public class Listing0914
    040 {
    041   public static void printTable(DoubleMethod meth)
    042   {
    043     System.out.println("Wertetabelle " + meth.toString());
    044     for (double x = 0.0; x <= 5.0; x += 1) {
    045       System.out.println(" " + x + "->" + meth.compute(x));
    046     }
    047   }
    048
    049   public static void main(String[] args)
    050   {
    051     printTable(new Times2());
    052     printTable(new MathExp());
    053     printTable(new Sqr());
    054     printTable(new MathSqrt());
    055   }
    056 }
| Listing0914.java

Listing 9.14: Funktionszeiger mit Interfaces

|  | In Abschnitt 43.3.2 wird gezeigt, wie man Funktionszeiger auf ähnliche Weise mit dem Reflection-API realisieren kann. |  |
Hinweis

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de