Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage |
<< | < | > | >> | API | Kapitel 10 - OOP III: Interfaces |
Es wurde bereits erwähnt, dass es in Java keine Mehrfachvererbung von Klassen gibt. Die möglichen Schwierigkeiten beim Umgang mit mehrfacher Vererbung und die Einsicht, dass das Erben nichttrivialer Methoden aus mehr als einer Klasse in der Praxis selten zu realisieren ist, 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.
Ein Interface ist eine besondere Form einer Klasse, die ausschließlich abstrakte Methoden und Konstanten enthält. Anstelle des Schlüsselworts class wird ein Interface mit dem Bezeichner interface deklariert. Alle Methoden eines Interface sind implizit abstrakt und öffentlich. Neben Methoden kann ein Interface auch Konstanten enthalten, die Definition von Konstruktoren ist allerdings nicht erlaubt.
Wir wollen uns ein einfaches Beispiel ansehen. Das folgende Listing definiert ein Interface Groesse, das die drei Methoden laenge, hoehe und breite enthält:
001 /* Groesse.java */ 002 003 public interface Groesse 004 { 005 public int laenge(); 006 public int hoehe(); 007 public int breite(); 008 } |
Groesse.java |
Diese Definition ähnelt sehr einer abstrakten Klasse und dient dazu, eine Schnittstelle für den Zugriff auf die räumliche Ausdehnung eines Objekts festzulegen. Wir wollen uns an dieser Stelle keine Gedanken über komplizierte Details wie zu verwendende Maßeinheiten oder Objekte mit mehr oder weniger als drei Dimensionen machen.
Durch das bloße Definieren eines Interface 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 implements-Klausel, hinter der der Name des zu implementierenden Interface angegeben wird. Der Compiler sorgt dafür, dass alle im Interface geforderten Methoden definitionsgemäß implementiert werden. Zusätzlich »verleiht« er der Klasse einen neuen Datentyp, der - wie wir noch sehen werden - ähnliche Eigenschaften wie eine echte Klasse hat.
Eine beispielhafte Implementierung des Interface Groesse könnte etwa von der schon bekannten Auto-Klasse vorgenommen werden:
001 /* Auto2.java */ 002 003 public class Auto2 004 implements Groesse 005 { 006 public String name; 007 public int erstzulassung; 008 public int leistung; 009 public int laenge; 010 public int hoehe; 011 public int breite; 012 013 public int laenge() 014 { 015 return this.laenge; 016 } 017 018 public int hoehe() 019 { 020 return this.hoehe; 021 } 022 023 public int breite() 024 { 025 return this.breite; 026 } 027 } |
Auto2.java |
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:
001 public class FussballPlatz 002 implements Groesse 003 { 004 public int laenge() 005 { 006 return 105000; 007 } 008 009 public int hoehe() 010 { 011 return 0; 012 } 013 014 public int breite() 015 { 016 return 70000; 017 } 018 } |
FussballPlatz.java |
Hier geben die Interface-Methoden konstante Werte zurück (in der Annahme, dass alle Fußballplätze gleich groß sind). Ebenso gut ist es möglich, dass die Größe von anderen Instanzmerkmalen abhängig ist und zur Implementierung des Interface etwas mehr Aufwand getrieben werden muss:
001 /* PapierBlatt.java */ 002 003 public class PapierBlatt 004 implements Groesse 005 { 006 public int format; //0=DIN A0, 1=DIN A1 usw. 007 008 public int laenge() 009 { 010 int ret = 0; 011 if (format == 0) { 012 ret = 1189; 013 } else if (format == 1) { 014 ret = 841; 015 } else if (format == 2) { 016 ret = 594; 017 } else if (format == 3) { 018 ret = 420; 019 } else if (format == 4) { 020 ret = 297; 021 } 022 //usw... 023 return ret; 024 } 025 026 public int hoehe() 027 { 028 return 0; 029 } 030 031 public int breite() 032 { 033 int ret = 0; 034 if (format == 0) { 035 ret = 841; 036 } else if (format == 1) { 037 ret = 594; 038 } else if (format == 2) { 039 ret = 420; 040 } else if (format == 3) { 041 ret = 297; 042 } else if (format == 4) { 043 ret = 210; 044 } 045 //usw... 046 return ret; 047 } 048 } |
PapierBlatt.java |
Die Art der Realisierung der vereinbarten Methoden spielt für das Implementieren eines Interface 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 und kann nicht dazu verwendet werden, Objekte zu instanzieren. |
|
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, Fußballplätze und Papierblätter daraus abgeleitet wären. Durch Implementieren des Groesse-Interface können wir die Verfügbarkeit der drei Methoden laenge, hoehe und breite dagegen unabhängig von ihrer eigenen Vererbungslinie garantieren.
Wir wollen uns ein einfaches Beispiel für die Anwendung des Interface ansehen. Dazu soll eine Methode grundflaeche entwickelt werden, die zu jedem Objekt, das das Interface Groesse implementiert, dessen Grundfläche (Länge mal Breite) berechnet:
001 /* Listing1005.java */ 002 003 public class Listing1005 004 { 005 public static long grundflaeche(Groesse g) 006 { 007 return (long)g.laenge() * g.breite(); 008 } 009 010 public static void main(String[] args) 011 { 012 //Zuerst erzeugen wir ein Auto2... 013 Auto2 auto = new Auto2(); 014 auto.laenge = 4235; 015 auto.hoehe = 1650; 016 auto.breite = 1820; 017 //Nun ein DIN A4-Blatt... 018 PapierBlatt blatt = new PapierBlatt(); 019 blatt.format = 4; 020 //Und zum Schluss einen Fußballplatz... 021 FussballPlatz platz = new FussballPlatz(); 022 //Nun werden sie ausgegeben 023 System.out.println("Auto: " + grundflaeche(auto)); 024 System.out.println("Blatt: " + grundflaeche(blatt)); 025 System.out.println("Platz: " + grundflaeche(platz)); 026 } 027 } |
Listing1005.java |
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.
Die Ausgabe des Programms ist:
Auto: 7707700
Blatt: 62370
Platz: 7350000000
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.
Auch der instanceof-Operator kann auf Interface-Namen angewendet werden. Eine alternative Implementierung der Methode grundflaeche, die mit allen Objekttypen funktioniert, könnte dann etwa so aussehen:
001 public static long grundflaeche(Object o) 002 { 003 long ret = 0; 004 if (o instanceof Groesse) { 005 Groesse g = (Groesse)o; 006 ret = (long)g.laenge() * g.breite(); 007 } 008 return ret; 009 } |
Diese Implementierung verhält sich für alle Objekte, die das Interface Groesse implementieren, so wie die vorige. Für alle übrigen Objekte wird 0 zurückgegeben.
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 |