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

10.4 Weitere Anwendungen von Interfaces



10.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 seine Konstanten. Es ist auch erlaubt, dass 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 durch den qualifizierten Ausdruck Klasse.Name aufzurufen, könnte ein 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 qualifizierten Klassennamen aufgerufen werden.

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

001 /* Listing1011.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 Listing1011
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 }
Listing1011.java
Listing 10.11: Konstanten in Interfaces

static import

Durch die implements-Anweisung können die Konstanten direkt verwendet werden, also ohne vorangestellte Interface-Namen. Diese praktische und von vielen Entwicklern verwendete Möglichkeit ist allerdings nicht unbedingt im Sinne des Erfinders. Die Implementierung eines Interface soll ja schließlich die Zugehörigkeit einer Klasse zu einem bestimmten Typ dokumentieren - nicht die Schreibfaulheit des Entwicklers.

In Java gibt es deshalb auch eine Möglichkeit, statische Teile aus Klassen und Interfaces unqualifiziert zu benutzen, ohne implements zu verwenden. Bei diesem, als static import bezeichneten Verfahren werden die benötigten statischen Bestandteile in einer speziellen import-Anweisung aufgelistet, bei der nach dem Schlüsselwort import static der qualifizierte Name des statischen Members folgt. Dieser kann dann im weiteren Verlauf der Klasse unqualifiziert verwendet werden. Sollen alle statischen Member auf einmal verfügbar gemacht werden, ist Klasse.* zu schreiben. Dieses Verfahren funktioniert sowohl mit Interfaces als auch mit Klassen und kann sowohl Methoden wie auch Membervariablen importieren.

Das folgende Beispiel zeigt die Anwendung dieses neuen Features am Beispiel der Klasse java.lang.Math. Die Methoden sqrt und sin sowie die Konstante PI können nach dem statischen Import der Klasse unqualifiziert verwendet werden:

001 /* Listing1012.java */
002 
003 import static java.lang.Math.*;
004 
005 public class Listing1012
006 {
007   public static void main(String[] args)
008   {
009     System.out.println(sqrt(2));
010     System.out.println(sin(PI));
011   }
012 }
Listing1012.java
Listing 10.12: static import

10.4.2 Implementierung von Flags

Einige Interfaces definieren weder Methoden noch Konstanten. Sie stellen stattdessen eine Art Flag, also einen logischen An-/Aus-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 42 ausführlich gewürdigt wird, wollen wir uns nachfolgend die Bedeutung des Interface 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, muss man clone überlagern und selbst dafür sorgen, dass 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 10.13: Implementierung einer tiefen Kopie

10.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, dass 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 Interface 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 10.14: 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 /* Listing1015.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 Listing1015
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 }
Listing1015.java
Listing 10.15: Funktionszeiger mit Interfaces

In Abschnitt 45.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, 7. Auflage, Addison Wesley, Version 7.0
 <<    <     >    >>   API  © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de