Advanced Java Services | Interfaces |
Ein Interface kann man auffassen als Spezialfall einer abstrakten Klasse. Treibt man das Konzept abstrakte Klasse auf die Spitze, so landet man bei einer Klasse, die nur noch abstrakte Methoden enthält.
public abstract class SehrAbstrakt { private int a ; // nicht sinnvoll public int b ; public abstract void abstractMethod1(); public abstract int abstractMethod2(String s); public abstract String abstractMethod3(int a); // ... // ... }
In der obigen Situation gibt es für die Variable a keine sinnvolle Verwendung. Wer kann sie benützen ? In der Klasse gibt es keine Methoden, die sie verwenden, außerhalb der Klasse ist sie nicht ansprechbar. Objekte einer Ableitung von SehrAbstrakt können sie auch nicht verwenden, weil sie auf den ererbten privaten Datenteil nicht direkt zugreifen können. In dieser Situation machen nur noch public Variable einen Sinn. Die Initialisierung von b oder der Zugriff auf b ist auch nicht direkt möglich, weil man ja ein Objekt braucht, um b zu benützen. Immerhin kann ein Objekt einer abgeleiteten Klasse darauf zugreifen. Das widerspricht jedoch dem Prinzip der Datenkapselung. Sinnvoller wäre an dieser Stelle eine statische Variable. Die läßt sich natürlich auch bei einer abstrakten Klasse über den Namen der Klasse ansprechen. Damit sind wir schon fast bei einem Interface.
public abstract class FastEinInterface { public static int a ; // statische Variable public static final int b=17 ; // statische Konstante, muß sofort initialisiert werden public abstract void abstractMethod1(); public abstract int abstractMethod2(String s); public abstract String abstractMethod3(int a); // ... // ... }
Von der Klasse zum Interface
public interface AnInterface { public int b=17 ; // automatisch static und final !! // statische Konstante, muß sofort initialisiert werden public void abstractMethod1(); // automatisch abstrakt public int abstractMethod2(String s); public String abstractMethod3(int a); // ... // ... }
Ein interface ist eine Sammlung von abstrakten Methoden und statischen Konstanten von jeweils beliebiger Anzahl. Bei den Methoden kann das Schlüsselwort abstract weggelassen werden, der Compiler ergänzt es automatisch. Bei den Konstanten kann sowohl der modifier static wie auch der modifier final weggelassen werden, beide werden vom Compiler ergänzt. Methoden sowie Konstanten können nur public sein. Auch dieses Schlüsselwort kann weggelassen werden.
Zweck des Begriffes ist es, eine abgeschwächte Form der Mehrfachvererbung zuzulassen. Dazu wird das neue Schlüsselwort implements eingeführt. Eine Klasse kann nur einmal von einer anderen Klasse erben, aber beliebig viele Interfaces implementieren:
public class Child extends Parent implements Face1, Face2, Face3 { }
Die obige Situation stellt sich graphisch wie folgt dar. Das Schlüsselwort implements wird in der Regel
weggelassen und nur durch den gestrichelten Pfeil symbolisiert.
Wir wissen bereits, daß in der obigen Situation Parent und Child typverwandt sind, d.h. folgende Zuweisung legal ist.
Parent p ; Child c = new Child(); p = c ;
Nun gibt es eine wichtige Ergänzung. Obwohl man von einem Interface keine Objekte anlegen kann, stellt jedes Interface einen eigenen Datentyp dar. Ein Objekt zu diesem Typ existiert jedoch erst dann, wenn es eine Klasse gibt, die dieses Interface implementiert und die abstrakten Methoden realisiert. Wir nehmen in der obigen Situation an, daß die Klasse Child sämtliche Methoden aus den Interfaces realisiert und somit von der Klasse Child auch Objekte angelegt werden können. Das folgende Beispiel zeigt dies. Man sieht, daß man die Zeiger vom Typ eines Interfaces genauso einsetzt wie einen Basiszeiger. Der Konstruktoraufruf mit new erfordert eine reale Klasse (//1), bei den nachfolgenden Zuweisungen spielen die Referenzen die Rolle eines Basiszeigers (//2). Es sind auch Aufrufe von Methoden möglich, jeder Interfacetyp kennt aber nur die Methoden, die er vereinbart hat.
public class Interface { public static void main(String[] args) { Face1 f1; Face1 f2; Face1 f3; Child ch = new Child(); // 1 f1 = ch; f2 = ch; f3 = ch; // 2 f1.faceMethod1(); // f1.childMethod(); cannot resolve symbol // f1.faceMethod2(); cannot resolve symbol } } class Parent { } class Child extends Parent implements Face1, Face2, Face3 { public void childMethod() {} public void faceMethod1() {} public void faceMethod2() {} public void faceMethod3() {} } interface Face1 { public void faceMethod1(); } interface Face2 { public void faceMethod2(); } interface Face3 { public void faceMethod3(); }
Fazit:
Der Name interface kommt natürlich nicht von ungefähr. Eine Klasse, die ein Interface implementiert, garantiert die Existenz der im interface vereinbarten Methoden und bietet damit eine Schnittstelle an. Eine dritte Klasse kann sich dann darauf verlassen, daß diese Methoden aufrufbar sind. Eine weitere Standardsituation ist folgende: Eine Methode hat in der Parameterliste den Typ eines Interfaces stehen. Jedes Objekt, dessen zugehörige Klasse dieses Interface implementiert, kann dann dieser Methode als Parameter übergeben werden. Die Klassen brauchen in keiner Weise über Vererbung verwandt zu sein. Innerhalb der Methode erscheinen sie als Objekte vom Typ des Interfaces und der Programmierer kann alle Methoden aufrufen, die der Interfacetyp vereinbart hat. Welchen Klassentyp sie haben, bleibt im verborgen. Er muß es auch nicht wissen, denn alle Objekte bieten ihm die gleiche Schnittstelle an.
Seit ich Java unterrichte, und das ist seit der Version 1.1. der Fall, bringe ich dieses Beispiel,
um das Konzept interface zu erläutern. Als ich dann die neue Version 1.2 heruntergeladen habe, habe
ich nicht schlecht gestaunt, daß die Entwickler von Java genau die gleiche Idee hatten und ein
Interface Comparable in die API aufgenommen haben...
Die Idee ist die folgende:
Wir können in unserer Klasse Sort neben den Arrays primitiver Datentypen auch Stringarrays und
StringsBufferarrays sortieren. Es wäre schön, wenn wir auch Arrays vom Typ Person sortieren könnten.
Dazu könnte man einen eigenen Sortiermethode für Personarrays schreiben. Diese müßte dann etwa mit
Hilfe von getNachname() einen Stringvergleich durchführen. Es gibt nun aber sehr viele andere
Klasse, deren Objekte man auch nach einem bestimmten Kriterium vergleichen könnte, für die also
Sortieren einen Sinn macht, jeden Tag können neue solche Klassen entstehen, die Anzahl der
Javaklassen ist unbegrenzt. Unsere Klasse Sort kann also unmöglich für jede Klasse eine eigene
Sortiermethode anbieten. Mit Hilfe eines Interfaces kann man eine sehr elegante Lösung des Problems
finden. Wir schieben zwischen der Klasse Sort und der Klasse Person ein Interface ein. Das Interface
vereinbart eine Vergleichsmethode nach dem Vorbild des Stringvergleichs. Wir nennen es Comparable.
Klasse Sort <---> Interface Comparable <--- Klasse Person
Unser Interface Comparable hat die folgende Gestalt:
public interface Comparable { public int compareTo(Comparable another) ; }
Der Entwickler der Klasse Sort hat von der Existenz dieses Interfaces erfahren und schreibt nun eine einzige zusätzliche Sortiermethode für den Typ Comparable. Diese schaut im Prinzip genauso aus wie die Sortierroutine für Strings.
public static void bubble(Comparable[] arr) { Comparable tmp ; for(int i=1 ; i < arr.length ; i++ ) for(int j = arr.length - 1 ; j >= i ; j--) if ( arr[j].compareTo(arr[j-1]) < 0 ) { tmp = arr[j-1] ; arr[j-1] = arr[j]; arr[j] = tmp; } }
Der Entwickler der Klasse Person erfährt nun von der Existenz der Klasse Sort und des Interfaces Comparable. Er implementiert das Interface und verpflichtet sich damit, die Methode compareTo() zu implementieren. Er überlegt sich die Bedeutung von größer und kleiner für seine Objekte und ergänzt seine Klasse um die neue Methode.
public class Person implements Comparable { private String vor , nach ; //... // ------------ Vom Interface Comparable geforderte Methode ------------ \\ public int compareTo(Comparable another) { return nach.compareTo( ((Person)another).nach ) ; } } // end class Person
Die obige einfache Implementierung vergleicht nur die Nachnamen und stützt sich auf die compareTo-Methode
für Strings.
Ein Anwendungsprogrammierer erhält die fertigcompilierten Klassen Sort.class, Comparable.class und
Person.class, liest die zugehörigen Dokumentationen und probiert das ganze folgendermaßen aus.
public class PersonDemo { public static void main(String args[]) { Person person[] = new Person[6] ; for(int i=0; i < person.length; i++) { person[i] = new Person(); System.out.print("Vorname : ") ; person[i].setVor( Stdin.stringEingabe() ); System.out.print("Nachname : ") ; person[i].setNach( Stdin.stringEingabe() ); } Sort.bubble(person); for(int i=0; i < person.length; i++) person[i].println(); } // end main } // end class PersonDemo
Und es funktioniert. Die Klasse Sort kann nun Arrays von x-beliebigen Klassentypen sortieren, auch
solche, die bei der Enwicklung von Sort.java noch garnicht existierten. Einzige Voraussetzung ist,
daß diese Klassen das Interface Comparable implementieren.
Das Schreiben eines Interfaces ist oft eine nahezu banale Angelegenheit. Es ist das Konzept, das diesen
Begriff so mächtig macht. Die entscheidende Arbeit liegt im Entwerfen des Interfaces, nicht im
Codieren.
Seit Java 1.2 gibt es in der API ein Interface Comparable, das fast identisch zu obigem Interface ist. Es sieht folgendermaßen aus:
public interface Comparable { public int compareTo(Object ob) ; }
Wir verwenden jetzt dieses Interface und die Klasse Person implementiert nun dieses:
public class Person implements java.lang.Comparable { private String vor , nach ; //... // -------- Vom Interface java.lang.Comparable geforderte Methode -------- \\ public int compareTo(Object another) { return nach.compareTo( ((Person)another).nach ) ; } } // end class Person
Wieder brauchen wir eine Sortierroutine, die unser PersonArray als Parameter übernimmt. Wir schreiben sie nicht neu, sondern greifen auf die Standardklasse Arrays zurück, die mit dem Interface java.lang.Comparable genauso zusammenarbeitet wie unser Klasse Sort mit unserem Comparable
Klasse Arrays <---> Interface java.lang.Comparable <--- Klasse Person
Ähnlich wie unserer Klasse Sort besteht auch die Klasse Arrays aus einer Sammlung statischer Methoden. Sie enthält u.a. die Methode static void sort(Object[] a). Dieser Methode können Arrays von beliebigen Klassen übergeben werden, vorausgesetzt die Klasse implementiert das Interface java.lang.Comparable. Versuchen Sie das als Übung.
Auch für Interfaces gibt es Vererbung. Die wichtige Einschränkung ist, daß ein Interface nur von
einem Interface erben kann, nie von einer Klasse. Durch diese (sinnvolle und notwendige) Einschränkung
wird die Vererbung bei Interfaces eine ziemlich einfache Angelegenheit. Wie in Diagrammen üblich
bedeutet ein durchgezogener Pfeil extends und ein gestrichelter Pfeil implements.
Im obigen Diagramm haben wir zwei Interfaces Face1 und Face2 und zwei Klassen Parent und RealChild.
Der Name der letzteren soll natürlich bedeuten, daß RealChild keine abstrakte Klasse ist. Diese
spezielle Variante der Mehrfachvererbung vermeidet die Probleme der allgemeinen Mehrfachvererbung
bei Klassen. Es folgt ein Beispiel einer möglichen Realisierung der obigen Hierarchie.
public class Interface3 { public static void main(String[] args) { Face1 f1; Face1 f2; Parent pa = new Parent(); RealChild ch = new RealChild(); f1 = pa; f2 = ch; } } interface Face1 { public void faceMethod1(); } interface Face2 extends Face1 { public void faceMethod2(); } class Parent implements Face1 { public void faceMethod1() { System.out.println("faceMethod1 von Parent") ; } } class RealChild extends Parent implements Face2 { public void faceMethod1() { System.out.println("faceMethod1 von Parent") ; } public void faceMethod2() { System.out.println("faceMethod2") ; } }
Überlegen sie sich, daß es nicht zu Widersprüchen kommt. Es ist egal, auf welchem Weg sie von Face1 zu RealChild kommen.
Weitere Realisierungen des Beispieldiagramms
Das Interface Comparable2