Programmieren / Java Interfaces und deren Anwendung Frage Nr. 5 I nt e r fa ce s und de r e n Anw e nd ung Ausarbeitung einer Maturafrage aus dem Fach P r o g r a m m i e r e n / Andreas Hechenblaickner 5CDH | HTBLA Kaindorf/Sulm Dipl.-Ing. Manfred Wilfling Erstelldatum: 23. Februar 2003 Letzte Überarbeitung: 2. Juni 2003 J a v a PUC | Interfaces und deren Anwendung Inhaltsverzeichnis Inhaltsverzeichnis Inhaltsverzeichnis 2 1 Grundlagen 3 1.1 Definition eines Interfaces 3 1.2 Implementierung eines Interfaces 3 1.3 Verwendung eines Interfaces 5 1.4 Mehrfachimplementierung 6 1.5 Vererbung von Interfaces 7 1.6 Ableiten von Interfaces 8 2 Anwendung von Interfaces 9 2.1 Konstanten in Interfaces 9 2.2 Implementierung von Flags 9 2.3 Nachbildung von Funktionszeigern 10 Folienvorschlag 12 Zusammenfassung 13 Quellen 13 HTBLA Kaindorf/Sulm Seite 2 von 13 PUC | Interfaces und deren Anwendung 1 Grundlagen Grundlagen In Java gibt es keine Mehrfachvererbung von Klassen. Die möglichen Schwierigkeiten beim Umgang mit mehrfacher Vererbung haben die Designer dazu veranlasst, dieses Feature nicht zu implementieren. Andererseits sah man es als wünschenswert an, dass Klassen eine oder mehrere Schnittstellendefinitionen erben können, und hat mit den Interfaces ein Ersatzkonstrukt geschaffen, das dieses Feature bietet. 1.1 Definition eines Interfaces Ein Interface ist eine besondere Form einer Klasse, die ausschließlich abstrakte Methoden und Konstanten enthält. Anstelle des Schlüsselwortes class wird ein Interface mit dem Bezeichner interface deklariert. Alle Methoden eines Interfaces sind implizit abstrakt und öffentlich. Neben Methoden kann ein Interface auch Konstanten enthalten, die Definition von Konstruktoren ist allerdings nicht erlaubt. Das folgende Listing definiert ein Interface Groesse, das die drei Methoden laenge(), hoehe() und breite() enthält: 1 public interface Groesse { 2 public int laenge(); 3 public int hoehe(); 4 public int breite(); 5 } «interface» Groesse +laenge() +hoehe() +breite() Abbildung 1: UML-Notation des Interfaces Groesse Diese Definition ähnelt sehr einer abstrakten Klasse und dient dazu, eine Schnittstelle für den Zugriff auf die räumliche Ausdehnung eines Objekts festzulegen. 1.2 Implementierung eines Interfaces Durch das bloße Definieren eines Interfaces wird die gewünschte Funktionalität aber noch nicht zur Verfügung gestellt, sondern lediglich beschrieben. Soll diese von einer Klasse tatsächlich realisiert werden, muss sie das Interface implementieren. Dazu erweitert sie die class-Anweisung um eine implementsKlausel, hinter der der Name des zu implementierenden Interfaces angegeben wird. HTBLA Kaindorf/Sulm Seite 3 von 13 PUC | Interfaces und deren Anwendung Grundlagen 1 public class Auto implements Groesse { 2 public String name; 3 public int leistung; 4 public int laenge; 5 public int hoehe; 6 public int breite; 7 8 public int laenge() { 9 return this.laenge; 10 } 11 12 public int hoehe() { 13 return this.hoehe; 14 } 15 16 public int breite() { 17 return this.breite; 18 } 19 } Auto «interface» Groesse +laenge() +hoehe() +breite() name: String leistung: int laenge: int hoehe: int breite: int +laenge() +hoehe() +breite() Abbildung 2: UML-Notation der Klasse Auto Wir haben die Klasse dazu um drei veränderliche Instanzmerkmale erweitert, die es uns erlauben, die vom Interface geforderten Methoden auf einfache Weise zu implementieren. Ebenso wie die Klasse Auto könnte auch jede andere Klasse das Interface implementieren und so Informationen über seine räumliche Ausdehnung geben: 1 public class FussballPlatz implements Groesse { 2 public int laenge() { 3 return 105000; 4 } 5 6 public int hoehe() { 7 return 0; 8 } 9 10 public int breite() { 11 return 70000; 12 } 13 } «interface» Groesse +laenge() +hoehe() +breite() Fussballplatz +laenge() +hoehe() +breite() Abbildung 3: UML-Notation der Klasse Fussballplatz HTBLA Kaindorf/Sulm Seite 4 von 13 PUC | Interfaces und deren Anwendung Grundlagen Die Art der Realisierung der vereinbarten Methoden spielt für das Implementieren eines Interfaces keine Rolle. Tatsächlich kommt es ausgesprochen häufig vor, dass Interfaces von sehr unterschiedlichen Klassen implementiert und die erforderlichen Methoden auf sehr unterschiedliche Weise realisiert werden. Eine Klasse kann ein Interface auch dann implementieren, wenn sie nicht alle seine Methoden implementiert. In diesem Fall ist die Klasse allerdings als abstract zu deklarieren. 1.3 Verwendung eines Interfaces Nützlich ist ein Interface immer dann, wenn Eigenschaften einer Klasse beschrieben werden sollen, die nicht direkt in seiner normalen Vererbungshierarchie abgebildet werden können. Hätten wir beispielsweise Groesse als abstrakte Klasse definiert, ergäbe sich eine sehr unnatürliche Ableitungshierarchie, wenn Autos und Fußballplätze daraus abgeleitet wären. Durch Implementieren des Groesse-Interfaces können sie die Verfügbarkeit der drei Methoden laenge(), hoehe() und breite() dagegen unabhängig von ihrer eigenen Vererbungslinie garantieren. 1 public class InterfaceDemo1 { 2 public static long grundflaeche(Groesse g) { 3 return (long)g.laenge() * g.breite(); 4 } 5 6 public static void main(String[] args) { 7 //Zuerst erzeugen wir ein Auto... 8 Auto auto = new Auto(); 9 auto.laenge = 4235; 10 auto.hoehe = 1650; 11 auto.breite = 1820; 12 13 //Und zum Schluß einen Fußballplatz... 14 FussballPlatz platz = new FussballPlatz(); 15 16 //Nun werden sie ausgegeben 17 System.out.println("Auto: " + grundflaeche(auto)); 18 System.out.println("Platz: " + grundflaeche(platz)); 19 } 20 } Das Programm erzeugt zunächst einige Objekte, die das Groesse-Interface implementieren. Anschließend werden sie an die Methode grundflaeche übergeben, deren Argument g vom Typ Groesse ist. Durch diese Typisierung kann der Compiler sicherstellen, dass nur Objekte "des Typs" Groesse an grundflaeche übergeben werden. Das ist genau dann der Fall, wenn das übergebene Objekt dieses Interface implementiert. HTBLA Kaindorf/Sulm Seite 5 von 13 PUC | Interfaces und deren Anwendung Grundlagen An diesem Beispiel kann man bereits die wichtigste Gemeinsamkeit zwischen abstrakten Klassen und Interfaces erkennen: Beide können im Programm zur Deklaration von lokalen Variablen, Membervariablen oder Methodenparametern verwendet werden. Eine Interface-Variable ist kompatibel zu allen Objekten, deren Klassen dieses Interface implementieren. 1.4 Mehrfachimplementierung Es ist durchaus möglich (und gebräuchlich), dass eine Klasse mehrere Interfaces implementiert. Sie muss dann zu jedem Interface alle darin definierten Methoden implementieren. Mit jedem implementierten Interface wird sie zu dem dadurch definierten Datentyp kompatibel. Eine Klasse, die n Interfaces implementiert, ist demnach zu n + 1 Datentypen (plus ihren jeweiligen Oberklassen) kompatibel: Der Vaterklasse, aus der sie abgeleitet wurde (bzw. der Klasse Object, falls keine extends-Klausel vorhanden war). Den n Interfaces, die sie implementiert. Wir könnten beispielsweise vorhin definierte Klasse Auto erweitern und sie die Interfaces Groesse und Comparable implementieren lassen: HTBLA Kaindorf/Sulm Seite 6 von 13 PUC | Interfaces und deren Anwendung Grundlagen 1 public class Auto2 implements Groesse, Comparable { 2 public String name; 3 public int erstzulassung; 4 public int leistung; 5 public int laenge; 6 public int hoehe; 7 public int breite; 8 9 public int laenge() { 10 return this.laenge; 11 } 12 13 public int hoehe() { 14 return this.hoehe; 15 } 16 17 public int breite() { 18 return this.breite; 19 } 20 21 public int compareTo(Object o) { 22 int ret = 0; 23 if (leistung < ((Auto2)o).leistung) { 24 ret = -1; 25 } else if (leistung > ((Auto2)o).leistung) { 26 ret = 1; 27 } 28 return ret; 29 } 30 } Auto2 «interface» Groesse +laenge() +hoehe() +breite() «interface» Comparable name: String leistung: int laenge: int hoehe: int breite: int +laenge() +hoehe() +breite() +compareTo(Object) +compareTo(Object) Abbildung 4: UML-Notation der Klasse Auto2 Nun sind Objekte dieses Typs sowohl zu Groesse als auch zu Comparable kompatibel (Hinweis: die Sortierung basiert in diesem Fall nicht auf der Größe des Autos, sondern auf seiner Leistung). 1.5 Vererbung von Interfaces Die Implementierung hätte noch etwas vereinfacht werden können, wenn wir uns zu Nutze gemacht hätten, dass eine Klasse die Interfaces seiner Basisklasse erbt: HTBLA Kaindorf/Sulm Seite 7 von 13 PUC | Interfaces und deren Anwendung Grundlagen 1 public class Auto3 extends Auto 2 implements Comparable { 3 4 public int compareTo(Object o) { 5 int ret = 0; 6 if (leistung < ((Auto3)o).leistung) { 7 ret = -1; 8 } else if (leistung > ((Auto3)o).leistung) { 9 ret = 1; 10 } 11 return ret; 12 } 13 } Auto3 erbt von Auto nicht nur die Implementierung, sondern auch die Klausel implements Groesse und erweitert sie um die Implementierung von Comparable. Damit ist Auto3 gleichwertig zu Auto2. 1.6 Ableiten von Interfaces Auch Interfaces selbst können abgeleitet werden. Ähnlich einer Klasse erbt das abgeleitete Interface alle Methodendefinitionen des Basis-Interfaces. Die implementierende Klasse muss also auch alle Methoden von allen übergeordneten Interfaces implementieren: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface EinDimensional { public int laenge(); } interface ZweiDimensional extends EinDimensional { public int breite(); } interface DreiDimensional extends ZweiDimensional { public int hoehe(); } interface VierDimensional extends DreiDimensional { public int lebensdauer(); } public class InterfaceDemo2 implements VierDimensional { public int laenge() { return 0; } public int breite() { return 0; } public int hoehe() { return 0; } public int lebensdauer() { return 0; } } HTBLA Kaindorf/Sulm Seite 8 von 13 PUC | Interfaces und deren Anwendung Anwendung von Interfaces 2 Anwendung von Interfaces 2.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, 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 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2.2 interface Debug { public static public static public static public static } final final final final boolean boolean boolean boolean FUNCTIONALITY1 FUNCTIONALITY2 FUNCTIONALITY3 FUNCTIONALITY4 = = = = false; true; false; false; public class InterfaceDemo3 implements Debug { public static void main(String[] args) { //... if (FUNCTIONALITY1) { System.out.println("..."); } //... if (FUNCTIONALITY2) { System.out.println("..."); } //... } } 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. Wir wollen uns nachfolgend die Bedeutung des Interfaces Cloneable ansehen. HTBLA Kaindorf/Sulm Seite 9 von 13 PUC | Interfaces und deren Anwendung Anwendung von Interfaces 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, 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. 2.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 CallbackFunktionen 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 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. HTBLA Kaindorf/Sulm Seite 10 von 13 PUC | Interfaces und deren Anwendung Anwendung von Interfaces 1 public interface DoubleMethod { 2 public doubel compute(double value); 3 } 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Exp implements DoubleMethod { public double compute(double value) { return Math.exp(value); } } class Sqrt implements DoubleMethod { public double compute(double value) { return Math.sqrt(value); } } class Times2 implements DoubleMethod { public double compute(double value) { return 2 * value; } } class Sqr implements DoubleMethod { public double compute(double value) { return value * value; } } public class InterfaceDemo4 { public static void printTable(DoubleMethod meth) { System.out.println("Wertetabelle " + meth.toString()); for (double x = 0.0; x <= 5.0; x += 1) { System.out.println(x + "->" + meth.compute(x)); } } public static void printTable(new printTable(new printTable(new printTable(new } main(String[] args) { Times2()); Exp()); Sqr()); Sqrt()); } HTBLA Kaindorf/Sulm Seite 11 von 13 PUC | Interfaces und deren Anwendung Folienvorschlag Folienvorschlag Grundlagen Definition Implementierung Verwendung Mehrfachimplementierung Vererbung Ableitung Anwendung Konstanten Implementierung von Flags Nachbildung von Funktionszeigern HTBLA Kaindorf/Sulm Seite 12 von 13 PUC | Interfaces und deren Anwendung Zusammenfassung Zusammenfassung In diesem Skriptum wurden folgende Themen behandelt: Definition, Implementierung und Verwendung von Interfaces. Implementierung mehrer Interfaces. Vererbung von Interfaces. Ableitung von Interfaces. Verwendung als Konstanten-Interface. Verwendung als Flag-Interface. Tiefe und flache Kopie und die Methode clone(). Nachbildung von Funktionszeigern. Quellen Handbuch der Java-Programmierung, 3. Auflage (http://www.javabuch.de) Java 2 – Designmuster (http://www.galileocomputing.de) Java ist auch eine Insel, 2. Auflage (http://www.galileocomputing.de) Sun Microsystems, Java Technologie Home Page (http://java.sun.com) HTBLA Kaindorf/Sulm Seite 13 von 13