Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage |
<< | < | > | >> | API | Kapitel 9 - OOP III: 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 |
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 |
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 |
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 |
In Abschnitt 43.3.2 wird gezeigt, wie man Funktionszeiger auf ähnliche Weise mit dem Reflection-API realisieren kann. |
|
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 |