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.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.1
 <<    <     >    >>   API  © 1998-2003 Guido Krüger, http://www.javabuch.de