Die Implementation von Schnittstellen in Java
Motivation Ein gutes Verständnis der Implementation von Schnittstellen ist für die Verwendung der graphischen Benutzeroberfläche Swing und vieler anderer Systeme von Java nötig.
Wiederholung zu Schnittstellen als Obertypen
Wir haben bisher schon gesehen, daß ein Referenztyp eine Klasse oder eine Schnittstelle sein kann. Bisher war es oft nicht wichtig, zwischen Schnittstellen und Klassen zu unterscheiden. Ein Unterschied zwischen Klassen und Schnittstellen wurde dann jedoch vorgestellt: Nach dem Schlüsselwort »new« bei einer Exemplarerzeugung kann keine Schnittstelle verwendet werden und dementsprechend liefert die Methode ».getClass()« eines Objekts auch niemals eine Schnittstelle. Der Typ eines Objekts ist immer eine Klasse und nie eine Schnittstelle, Schnittstellen können nur Typen von Ausdrücken sein.
Wir haben Schnittstellen als Obertypen (beispielsweise ist die Schnittstelle »java.lang.CharSequence« ein Obertyp der Klasse »java.lang.String«) oder als Typen von Ausdrücken und Namen kennengelernt.
Wenn eine Schnittstelle ein Obertyp einer Klasse ist, dann sagt man, daß diese Klasse jene Schnittstelle implementiere. Die Obertypbeziehung bedingt dabei auch, daß diese Klasse alle Methoden jener Schnittstelle implementiert.
Wenn eine Klasse eine Schnittstelle implementiert, muß sie alle Methoden jener Schnittstelle unterstützen.
- ? Übungsfrage
- Welche Schnittstellen implementiert die Klasse »java.lang.String«?
- ? Übungsfrage
- Welche Klassen implementieren die Schnittstelle »java.lang.CharSequence«?
Die gemeinsame Schnittstelle »java.lang.CharSequenze« sorgt für gleiche Schreibweise und Bedeutung von Methoden wie »length()«. (Es gibt nicht unübersichtlich je nach der Klasse »length« mal als Feld, mal mit einer Namensvarianten wie »getLength()« oder »size()«, es gibt bei dieser Methode nicht unterschiedliche Rückgabespezifikationen wie »int« oder »double« oder unterschiedliche Bedeutungen.) Dies erleichtert auch das Kennenlernen der verschiedenen Klassen, da die gemeinsame Schnittstelle nur einmal gelernt werden muß.
- ? Übungsfrage
- Welche der folgenden Aufrufe sind erlaubt?
- »javax.lang.model.SourceVersion.isName( new java.lang.String( "RELEASE_0" ))«
- »javax.lang.model.SourceVersion.isName( new java.lang.StringBuilder( "RELEASE_0" ))«
- »javax.lang.model.SourceVersion.valueOf( new java.lang.String( "RELEASE_0" ))«
- »javax.lang.model.SourceVersion.valueOf( new java.lang.StringBuilder( "RELEASE_0" ))«
- ● Welche der beiden Methoden »isName« oder »valueOf« ist also universeller anwendbar (nützlicher)?
- ● Was lernen wir daraus über Schnittstellen?
Die Implementation einer Schnittstelle
Bei der Deklaration einer Klasse kann nach dem Klassennamen das Schlüsselwort »implements« gefolgt vom Namen einer Schnittstelle geschrieben werden, wie das folgende Programmbeispiel zeigt.
Dadurch wird die Implementation jener Schnittstelle durch die Klasse deklariert, wir sagen dann auch, daß diese Klasse jene Schnittstelle implementiere.
Dann müssen alle Methoden jener Schnittstelle implementiert werden, das heißt, daß sie als Methoden (einschließlich eines Blocks) deklariert werden müssen. Die implementierten Methoden einer Schnittstelle müssen dabei ohne »static« und mit »public« deklariert werden.
Die Schnittstelle »java.lang.Runnable« enthält genau eine Methode »void run()«, das folgende Beispiel zeigt die Implementation jener Schnittstelle durch die Klasse »Main«.
Main.java
public final class Main implements java.lang.Runnable
{ public void run(){ java.lang.System.out.println( "run" ); }public static void main( final java.lang.String[] args )
{ new Main().run(); }}
Schnittstellen als Parametertypen
Das folgende Programm zeigt die Verwendung der Schnittstelle »java.lang.Runnable« als Parametertyp. In diesem Programm wäre zwar auch »Main« also Parametertyp möglich, doch eröffnet die Verwendung der Schnittstelle »java.lang.Runnable« der Methode »execute« mehr Anwendungsmöglichkeit: Diese Methode kann so für Objekte aller möglichen Klassen (und nicht nur der Klasse »Main«) verwendet werden, solange jene Klassen nur die Schnittstelle »java.lang.Runnable« implementieren. Die Beschränkung auf Argumente vom Typ »Main« wäre eine unnötige Einschränkung.
Man spricht hier auch von einer Rückrufmethode : Zuerst wird in der Methode »main« die Methode »execute« aufgerufen, diese ruft dann ihrerseits eine Methode des ihr beim Aufruf angegebenen Main -Objekts auf, also ruft sie gewissermaßen „zurück“.
Main.java
public final class Main implements java.lang.Runnable
{ public void run(){ java.lang.System.out.println( "run" ); }public static void execute( final java.lang.Runnable runnable )
{ runnable.run(); }public static void main( final java.lang.String[] args )
{ Main.execute( new Main() ); }}
Die Implementation mehrerer Schnittstellen
- Aussprachehinweis
- Executor ˌɛg ˈzɛk jə tɚ
Main.java
public final class Main implements java.lang.Runnable, java.util.concurrent.Executor
{ public void run(){ java.lang.System.out.println( "run" ); }public void execute( final java.lang.Runnable runnable ){ runnable.run(); }
public static void main( final java.lang.String[] args )
{ new Main().run();
new Main().execute( new Main() ); }}java.lang.System.out
run
run
»@java.lang.FunctionalInterface«
Mit »@java.lang.FunctionalInterface« kann eine Schnittstelle als Methodenschnittstelle gekennzeichnet werden.
- Synopsis von »java.lang.Runnable«
@java.lang.FunctionalInterface public interface Runnable
{ public void run(); }
Anmerkungen zu Schnittstellen
Schnittstellen erweisen sich bei der modernen Java -Programmierung als sehr nützlich. Sie können aber nur für nicht-statische Methoden verwendet werden, nicht für statische Methoden. Unter anderem deswegen, vermeiden viele erfahrene Java -Programmierer statische Methoden so weit wie möglich und bevorzugen nicht-statische Methoden. Manche machen in der statischen main -Methode, die ja den Startpunkt eines Java -Programms darstellt, grundsätzlich nichts anderes, als eine nicht-statische Methode aufzurufen, die dann das eigentliche Programm enthält, und schreiben so viele Methodendeklarationen wie möglich als Deklarationen nicht-statischer Methoden.
Übungsfragen
Welche der im folgenden deklarierten Klassen implementieren die Schnittstelle »java.lang.Runnable« richtig?
/* A */ public final class Main { public void run(){} }
/* B */ public final class Main { void run(){} }
/* C */ public final class Main { public void runnable(){} }
/* D */ public final class Main { void runnable(){} }
/* E */ public final class Main implements java.lang.Runnable { public void run(){} }
/* F */ public final class Main implements java.lang.Runnable { void run(){} }
/* G */ public final class Main implements java.lang.Runnable { public void runnable(){} }
/* H */ public final class Main implements java.lang.Runnable { void runnable(){} }
/* I */ public final class Main { public int run(){} }
/* J */ public final class Main { int run(){} }
/* K */ public final class Main { public int runnable(){} }
/* L */ public final class Main { int runnable(){} }
/* M */ public final class Main implements java.lang.Runnable { public int run(){} }
/* N */ public final class Main implements java.lang.Runnable { int run(){} }
/* O */ public final class Main implements java.lang.Runnable { public int runnable(){} }
/* P */ public final class Main implements java.lang.Runnable { int runnable(){} }
Bedeutung von »invokeLater«
- Wir werden etwas später ein Verb »invokeLater« kennenlernen. Was bedeutet die englische Verbalphrase “to invoke later ” auf deutsch?
Übungsaufgaben (Grundaufgaben)
= Schnittstelle »javax.lang.Runnable«
- Implementieren Sie in der Klasse »Main« die Schnittstelle »java.lang.Runnable«, so daß die run -Methode den Text »Hallo, Welt« ausgibt. Übergeben Sie dann (in der main -Methode) der Methode »javax.swing.SwingUtilities.invokeLater« ein Exemplar der Klasse »Main«.
- Hinweis: Bei dieser Übungsaufgabe kann die Lösung schrittweise erarbeitet werden, indem man jeden Satz der Aufgabenstellung einzeln abarbeitet.
= Objekte mit Gedächtnis
- Schreiben Sie eine Implementation der Schnittstelle »java.lang.Runnable«, die eine Methode mit dem Deskriptor »void run()« deklariert, welche eine Zahl ausgibt, die ihr zuvor als Argument einer Methode mit dem Deskriptor »void accept(int)« übergeben wurde.
- (Der Deskriptor einer Methode ist die Kombination aus dem Aufruftyp dieser Methode und der Signatur dieser Methode. Die Signatur einer Methode ist die Kombination aus dem Methodenbezeichner dieser Methode und der eingeklammerten Parameterliste dieser Methode.)
= Implementation mehrerer Schnittstellen
- Schreiben Sie eine Klasse »Main«, welche sowohl »java.lang.Runnable« als auch »java.awt.event.ActionListener« implementiert und auch die bekannte statische main -Methode enthält. Dabei wird kein besonderes Verhalten von dieser Implementation verlangt – das heißt, daß die Aufgabe richtig gelöst wurde, wenn die Implementierung beider Schnittstellen deklariert wurde und der Compiler das Programm ohne Fehlermeldungen akzeptiert; die nicht-statischen Methoden müssen aber nichts Bestimmtes ergeben oder tun.
Übungsaufgaben (Reserveaufgaben)
== Schnittstelle »javax.swing.text.Position«
- Implementieren Sie in der Klasse »Main« die Schnittstelle »javax.swing.text.Position«.
- Es reicht, wenn Ihre Implementation vom Compiler ohne Fehlermeldung akzeptiert wird; das Verhalten der Methoden muß nicht so sein, wie in der Dokumentation dieser Schnittstelle beschrieben. Es reicht also, die Methode »getOffset()« irgendwie zu implementieren; die Beschreibung von »Position.Bias« in der Dokumentation dieser Schnittstelle kann ignoriert werden.
== Schreiben und erproben Sie eine Implementation »AppendableImplementation« der Schnittstelle »java.lang.Appendable«.
- Grundaufgabe: Es reicht, wenn Ihre Implementation vom Compiler ohne Fehlermeldung akzeptiert wird, das Verhalten der Methoden muß nicht so sein, wie in der Dokumentation dieser Schnittstelle beschrieben. (Hierfür sind keine Kenntnisse der englischen Sprache notwendig.)
- Zusatzaufgabe: Implementieren Sie die Schnittstelle so, daß auch das Verhalten der Methoden so ist, wie in der Dokumentation der Schnittstelle beschrieben. Das „Anhängen“ soll dabei durch Ausgeben des anzuhängenden Textes erfolgen.
- Zusatzaufgabe: Implementieren Sie die Schnittstelle so, daß auch das Verhalten der Methoden so ist, wie in der Dokumentation der Schnittstelle beschrieben. Das „Anhängen“ soll dabei durch Anhängen an einen Text der Klasse »StringBuilder« erfolgen. Fügen Sie eine Methode »toString()« hinzu, welche das Ergebnis alle Anhängevorgänge als String liefert.
== Schreiben und erproben Sie eine Implementation »ChecksumImplementation« der Schnittstelle »java.util.zip.Checksum«.
- Grundaufgabe: Es reicht, wenn Ihre Implementation vom Compiler ohne Fehlermeldung akzeptiert wird, das Verhalten der Methoden muß nicht so sein, wie in der Dokumentation dieser Schnittstelle beschrieben. (Hierfür sind keine Kenntnisse der englischen Sprache notwendig.)
- Zusatzaufgabe: Implementieren Sie die Schnittstelle so, daß auch das Verhalten der Methoden so ist, wie in der Dokumentation der Schnittstelle beschrieben. (Hierfür sind eventuell Kenntnisse der englischen Sprache und einiger Fachbegriffe nötig.)
Quellenangabe *
Das folgende Zitate gibt noch eine Quelle zum Thema der Schreibung von Bezeichnern mit Majuskeln (wie bei »MAX_VALUE«) für interessierte Leser an.
- JLS7, 6 Names, 6.1 Declaration, Constant Names
- The names of constants in interface types should be, and final variables of class types may conventionally be, a sequence of one or more words, acronyms, or abbreviations, all uppercase, with components separated by underscore "_" characters.
- JLS7, 6 Namen, 6.1 Deklarationen, Konstantennamen (Übersetzung)
- Die Namen von Konstanten in Schnittstellentypen sollten eine Folge von einem Wort oder mehreren Wörtern, Akronymen oder Abkürzungen sein, vollständig mit Großbuchstaben geschrieben, wobei die Komponenten durch einen Grundstrich getrennt sind. Die Namen von final-Variablen in Klassen dürfen per Konvention so geschrieben werden.