Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage |
<< | < | > | >> | API | Kapitel 10 - 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 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 |
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 |
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 |
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 |
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 |
![]() |
![]() |
In Abschnitt 45.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, 7. Auflage, Addison Wesley, Version 7.0 |
<< | < | > | >> | API | © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de |