Titel   Inhalt   Suchen   Index   API  Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung
 <<    <     >    >>  Kapitel 9 - OOP III: Interfaces

9.1 Grundlagen



Es wurde bereits erwähnt, daß es in Java keine Mehrfachvererbung von Klassen gibt. Die möglichen Schwierigkeiten beim Umgang mit mehrfacher Vererbung und die Einsicht, daß das Erben nichttrivialer Methoden aus mehr als einer Klasse in der Praxis selten zu realisieren ist, haben die Designer dazu veranlaßt, dieses Feature nicht zu implementieren. Andererseits sah man es als wünschenswert an, daß Klassen eine oder mehrere Schnittstellendefinitionen erben können, und hat mit den Interfaces ein Ersatzkonstrukt geschaffen, das dieses Feature bietet.

9.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.

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
Listing 9.1: Definition eines Interfaces

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.

9.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, muß sie das Interface implementieren . Dazu erweitert sie die class-Anweisung um eine implements-Klausel, hinter der der Name des zu implementierenden Interfaces angegeben wird. Der Compiler sorgt dafür, daß 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 Interfaces 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
Listing 9.2: Implementierung eines Interfaces

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
Listing 9.3: Die Klasse FussballPlatz

Hier geben die Interface-Methoden konstante Werte zurück (in der Annahme, daß alle Fußballplätze gleich groß sind). Ebenso gut ist es möglich, daß die Größe von anderen Instanzmerkmalen abhängig ist und zur Implementierung des Interfaces etwas mehr Aufwand getrieben werden muß:

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
Listing 9.4: Die Klasse PapierBlatt

Die Art der Realisierung der vereinbarten Methoden spielt für das Implementieren eines Interfaces keine Rolle. Tatsächlich kommt es ausgesprochen häufig vor, daß Interfaces von sehr unterschiedlichen Klassen implementiert und die erforderlichen Methoden auf sehr unterschiedliche Weise realisiert werden.

 Hinweis 

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.

 Tip 

9.1.3 Verwenden 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, Fußballplätze und Papierblätter 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.

Wir wollen uns ein einfaches Beispiel für die Anwendung des Interfaces 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 /* Listing0905.java */
002 
003 public class Listing0905
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 Schluß 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 }
Listing0905.java
Listing 9.5: Verwendung eines Interfaces

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, daß 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 Interfacenamen 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 }
Listing 9.6: Der instanceof-Operator auf Interfaces

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   API  Go To Java 2, Zweite Auflage, Addison Wesley, Version 2.0
 <<    <     >    >>  © 2000 Guido Krüger, http://www.gkrueger.com