Java Interface: Strukturen für Klassen vorgeben
Die Programmiersprache Java bietet vielfältige Möglichkeiten, um ein Programm zu strukturieren. Eine von ihnen ist das Interface. Damit lassen sich bestimmte Regeln vorgeben, anhand derer sich später Klassen erstellen lassen. Das sorgt für einheitliche und klare Strukturen. Dieser Artikel stellt vor, was ein Java Interface ist und wie Sie dieses erstellen und anwenden.
Was ist ein Java Interface?
Um zu verstehen, was ein Java Interface ist, ist es sinnvoll, sich zunächst mit der Vererbung in Java auseinanderzusetzen. Hierbei handelt es sich um ein wichtiges Grundprinzip der objektorientierten Programmierung. Die Vererbung sagt aus, dass es möglich ist, eine Klasse von einer anderen Klasse abzuleiten. Die abgeleitete Klasse erhält auf diese Weise alle Attribute und Methoden der übergeordneten Klasse. Diese kann sie dann um eigene Attribute und Methoden erweitern. Außerdem ist es möglich, Methoden der übergeordneten Klasse zu überschreiben.
Das Prinzip der Vererbung
Die Vererbung dient dazu, verschiedene Objekte in hierarchische Strukturen zu gliedern. Als Beispiel kann man sich vorstellen, dass eine Werkstatt ein Programm für die Verwaltung der Fahrzeuge erstellt, die die Kunden zur Reparatur abgeben. Dabei gibt es viele Eigenschaften und Aktionen, die für alle Fahrzeuge von Bedeutung sind – unabhängig davon, um welchen Typ es sich dabei handelt. Dafür ist es sinnvoll, eine gemeinsame Klasse zu erstellen. Allerdings weisen die verschiedenen Fahrzeugtypen – wie etwa Autos, Lkws und Motorräder – jeweils auch einige Besonderheiten auf. Wenn man diese nun in die gemeinsame Klasse aufnimmt, wird das Programm sehr unübersichtlich, da die Attribute und Methoden nicht auf alle Objekte des entsprechenden Typs zutreffen. Daher ist es besser, mit der Vererbung zu arbeiten. Das bedeutet, dass Sie zunächst eine übergeordnete Klasse erstellen, die alle gemeinsamen Eigenschaften festhält. Davon leiten Sie dann für die einzelnen Untergruppen eigene Klassen ab, in denen Sie alle spezifischen Details festlegen. Auf diese Weise sind für jedes Objekt genau die Strukturen verfügbar, die es benötigt. Dennoch haben wir mit der Klasse Fahrzeug eine übergeordnete Kategorie erstellt, die den Bezug deutlich macht. Außerdem spart diese Vorgehensweise etwas Arbeit, da es für die untergeordneten Klassen nicht notwendig ist, die gemeinsamen Attribute und Methoden neu zu definieren.
Abstrakte Klassen und Methoden
Wenn wir als übergeordnete Struktur eine gewöhnliche Klasse verwenden, müssen wir darin alle Methoden implementieren. Die abgeleiteten Klassen können diese dann in genau dieser Form übernehmen oder sie überschreiben. Manchmal kommt es jedoch vor, dass die Implementierung in allen abgeleiteten Klassen ganz unterschiedlich ist. In diesem Fall ist es nicht sinnvoll, bereits in der übergeordneten Klasse eine Implementierung zu erstellen. Für diesen Fall können wir eine abstrakte Methode verwenden. Diese gibt lediglich vor, dass die entsprechende Methode vorhanden sein muss. Außerdem legt sie die Übergabe- und Rückgabewerte fest. Eine Implementierung ist jedoch nicht vorhanden. Dafür sind dann die abgeleiteten Klassen zuständig. Um den Unterschied zu verdeutlichen, soll nun zunächst eine gewöhnliche Methode und daraufhin eine abstrakte Methode dargestellt werden.
Gewöhnliche Methode:
Abstrakte Methode:
public abstract int kilometerstandAusgeben();
Eine abstrakte Methode müssen wir mit dem Begriff abstract auszeichnen. Sie muss in einer abstrakten Klasse stehen. Diese muss ebenfalls mit diesem Schlüsselbegriff gekennzeichnet sein. Wenn wir eine Klasse von einer abstrakten Klasse ableiten, die eine abstrakte Methode enthält, muss sie zwingend eine Implementierung dieser abstrakten Methode enthalten.
Bei der Verwendung abstrakter Klassen ist es wichtig, darauf zu achten, dass es nicht möglich ist, diese für die Instanziierung eines Objekts zu verwenden. Sie dienen lediglich als Vorlage für andere Klassen.
Das Interface: nur für abstrakte Methoden vorgesehen
Eine abstrakte Klasse kann sowohl gewöhnliche als auch abstrakte Methoden enthalten. Außerdem ist es hier möglich, wie bei normalen Klassen Attribute zu definieren, die dann an die abgeleitete Klasse vererbt werden. Ein Interface weist gewisse Ähnlichkeiten zu einer abstrakten Klasse auf – deshalb war es auch wichtig, diese für die Herleitung genau zu beschreiben. Allerdings geht es hierbei noch einen Schritt weiter. Das Interface weist ausschließlich abstrakte Methoden auf. Gewöhnliche Methoden können wir hier nicht definieren.
Auch bei den Attributen gibt es einige Besonderheiten. Diese weisen in Interfaces stets die Eigenschaften public, static und final auf. Insbesondere der letzte Aspekt stellt einen wichtigen Unterschied zu abstrakten Klassen dar. Wenn ein Attribut als final definiert wird, bedeutet das, dass sich sein Wert nicht verändern darf. Es handelt sich daher um eine Konstante. Das stellt eine enorme Beeinträchtigung für die spätere Arbeit mit den abgeleiteten Klassen und Objekten dar. Deshalb ist es ausgesprochen unüblich, Attribute in einem Interface zu definieren.
Da in einem Interface alle Methoden abstrakt sein müssen, ist es hier nicht notwendig, diese gesondert auszuzeichnen. Statt des oben dargestellten Codes verwenden wir daher folgenden Ausdruck:
public int kilometerstandAusgeben();
Genau wie bei abstrakten Klassen ist es auch bei einem Interface nicht möglich, direkt ein Objekt damit zu erzeugen. Hierfür ist es zunächst notwendig, eine Klasse vom Interface abzuleiten.
Ein Java Interface erstellen
Nachdem wir die grundlegenden Eigenschaften eines Interfaces kennengelernt haben, ist es nun an der Zeit, sich der Praxis zuzuwenden. In diesem Abschnitt lernen Sie, wie sie ein Interface erstellen. Dafür verwenden wir das bereits angerissene Beispiel der Reparaturwerkstatt. Der Werkstattbesitzer will bei allen Fahrzeugen den Kilometerstand, die Marke und das Modell festhalten. Deshalb ist dafür jeweils eine Methode erforderlich, um die entsprechenden Werte auszugeben. Diese wollen wir daher in unserem Interface festhalten. Wenn ein Fahrzeug zur Reparatur gebracht wird, muss es der Mechaniker zunächst inspizieren, um festzustellen, welche Schäden dabei aufgetreten sind. Diese soll er dann zum entsprechenden Objekt hinzufügen. Auch hierfür ist eine entsprechende Methode erforderlich.
Das Interface leiten wir nun mit dem Ausdruck interface ein. Danach steht der Name, den wir ihm geben wollen – in unserem Beispiel Fahrzeuge. Anschließend fügen wir alle benötigten Methoden ein. Hierbei handelt es sich um abstrakte Methoden, die wir nach dem oben angegebenen Muster erstellen:
Eine Klasse mit dem Interface erstellen
Um mit einem Interface arbeiten zu können, müssen wir eine Klasse davon ableiten. Das stellt daher die nächste Aufgabe dar. Dafür verwenden wir wieder den Schlüsselbegriff class. Da die Klasse öffentlich sein soll, stellen wir den Ausdruck public voran – wobei dies nicht zwingend notwendig ist, da es sich hierbei bereits um den Standard-Wert für Klassen handelt. Danach müssen wir sie vom Interface ableiten. Im Gegensatz zur Vererbung bei Klassen verwenden wir hierfür jedoch nicht den Ausdruck extends, sondern implements:
public class Auto implements Fahrzeug
Danach erstellen wir wie gewohnt unsere Klasse – indem wir die Attribute festlegen, einen Konstruktor erstellen und Methoden definieren. Dabei müssen wir lediglich darauf achten, dass wir alle Methoden, die wir im Interface vorgegeben haben, hier implementieren müssen. Sonst kommt es zu einem Fehler. Durch die entsprechenden Methoden ist meistens auch implizit vorgegeben, welche Attribute die Klasse enthalten soll. Wenn wir beispielsweise den Kilometerstand ausgeben möchten, ist es sinnvoll, dass auch ein Attribut für diesen Wert vorhanden ist – auch wenn das Interface dies nicht zwingend vorschreibt. Wenn wir uns weitestgehend auf die durch das Interface vorgegebenen Elemente beschränken, könnte die zugehörige Klasse wie folgt aussehen:
Nun können wir noch ein kleines Hauptprogramm erstellen. Dieses dient lediglich dazu, ein entsprechendes Objekt zu erstellen und alle Methoden aufzurufen. Das Ergebnis ist in der nachfolgenden Abbildung zu sehen:
Ein Interface erweitern
Auch bei der Verwendung von Interfaces sind mehrstufige Hierarchien möglich. Das bedeutet, dass Sie ein Interface von einem anderen Interface ableiten können. Als Beispiel können wir uns vorstellen, dass der Reparaturbetrieb für Lkws verschiedene Werkstätten nutzt. Diese wählt er in Abhängigkeit vom Gewicht des Fahrzeugs aus. Um die richtige Werkstatt zuzuweisen, ist daher eine Methode gewichtAusgeben() notwendig. Selbstverständlich wäre es nun möglich, direkt eine Klasse für Lkws zu erstellen und darin die entsprechende Klasse zu implementieren. Für unser Beispiel gehen wir jedoch davon aus, dass später noch eine genauere Unterteilung stattfinden soll – beispielsweise in Sattelschlepper, Kipper und Kleinlastwagen. Die entsprechenden Klassen sollen erst auf dieser Ebene erstellt werden. Um sicherzustellen, dass all diese Klassen die Methode gewichtAusgeben() enthalten, führen wir daher ein weiteres Interface ein. Dieses soll jedoch alle Methoden enthalten, die wir bereits im Interface Fahrzeug vorgegeben haben.
Daher leiten wir das neue Interface von diesem ab. Hierfür verwenden wir wie bei Klassen den Begriff extends. Das abgeleitete Interface könnte dann so aussehen:
Das stellt sicher, dass die Klassen, die wir später vom Interface Lkw ableiten, alle Methoden, die wir im Interface Fahrzeug definiert haben und zusätzlich die Methode gewichtAusgeben() enthalten.
Mehrere Interfaces für eine Klasse verwenden
Wenn wir bei der Vererbung ausschließlich mit Klassen arbeiten, ist es nicht möglich, eine Klasse gleichzeitig von zwei (oder mehr) anderen Klassen abzuleiten. Hierfür können wir nur eine einzige Klasse verwenden. Nun könnte man einwenden, dass auch mehrfache Vererbungen möglich sind – dass wir also eine Klasse von einer Klasse ableiten können, die ihrerseits wieder von einer weiteren Klasse abgeleitet ist. Das setzt jedoch voraus, dass die Klasse, die in der mittleren Position steht, auch ein Bestandteil der Kategorie ist, die in der übergeordneten Klasse definiert ist. Trifft das nicht zu, ist es nicht möglich, mit Klassen zu arbeiten.
Da diese Ausführung nicht ganz einfach verständlich ist, wollen wir dies nun an einem Beispiel verdeutlichen. Dazu gehen wir davon aus, dass unsere Reparaturwerkstatt nicht nur Fahrzeuge repariert, sondern auch Zubehörartikel verkauft. Zu diesem Zweck erstellen wir in unserem Programm ein weiteres Interface, das wir Artikel nennen. Um das Beispiel so einfach wie möglich zu halten, gibt dieses nur eine einzige Methode vor: preisAusgeben(). Das führt zu folgendem Code:
Nun stellen wir uns vor, dass der Betrieb auch noch Gebrauchtwagen verkauft. Auf der einen Seite handelt es sich hierbei um Fahrzeuge. Es ist wichtig, die Marke, das Modell und den Kilometerstand ausgeben zu können. Auch hierbei ist es notwendig, Beschädigungen aufzunehmen, falls diese vorliegen. Daher ist es sinnvoll, die Klasse vom Interface Fahrzeug abzuleiten, das alle zugehörigen Methoden vorgibt.
Allerdings handelt es sich hierbei auch um einen Artikel, der zum Verkauf steht. Daher ist die Methode preisAusgeben() erforderlich, um den Verkaufspreis anzuzeigen. Daher ist auch eine Ableitung vom Interface Artikel sinnvoll. Das ist ganz einfach möglich. Dazu müssen wir bei der Gestaltung der Klasse lediglich beide Interfaces angeben – durch ein Komma voneinander getrennt:
Nun können wir auch noch das Hauptprogramm so anpassen, dass es ein Objekt vom Typ Gebrauchtwagen erzeugt und auch die Methode preisAusgeben() aufruft. Das Ergebnis ist wieder in der nachfolgenden Abbildung zu sehen:
Das zeigt einen wichtigen Unterschied zu abstrakten Klassen auf. Mit diesen wäre eine solche Struktur nicht möglich, da wir die Klasse Gebrauchtwagen nicht von zwei anderen Klassen ableiten können. Auch wäre es nicht möglich, eine Struktur mit drei Hierarchie-Ebenen zu gestalten. Da es sich nicht bei allen Fahrzeugen um Artikel handelt, die zum Verkauf stehen, können wir die Klasse Fahrzeuge nicht von der Klasse Artikel ableiten. Umgekehrt handelt es sich jedoch auch nicht bei allen Artikeln um Fahrzeuge, sodass auch eine derartige Ableitung nicht möglich ist. Daher ist hierbei die Verwendung von Interfaces zu empfehlen.
Wann ist die Verwendung von Interfaces sinnvoll
Nachdem wir die Eigenschaften und den Aufbau von Interfaces erklärt haben, bleibt noch die Frage zu klären, wann ihre Verwendung sinnvoll ist. Grundsätzlich besteht hierbei kein großer Unterschied zu abstrakten Klassen. Daher wäre es meistens möglich, auch diese Form der Implementierung zu wählen. Allerdings zeichnen sich Interfaces dadurch aus, dass sie nur ein Minimum an Informationen preisgeben. Sie geben lediglich vor, welche Methoden enthalten sein müssen und welche Übergabe- und Rückgabewerte diese aufweisen. Dabei handelt es sich um die essenziellen Informationen, die für die Anwendung der abgeleiteten Klassen notwendig sind. Das gibt Ihnen bei einem größeren Projekt die Möglichkeit, anderen Anwendern nur diese Details anzuzeigen. Alle anderen Informationen können Sie durch die Verwendung von Interfaces verbergen. Das erhöht die Sicherheit.
Ein weiterer Grund, der für die Verwendung von Interfaces spricht, wurde im vorherigen Abschnitt dargestellt. Hierbei ist es möglich, eine Klasse von zwei verschiedenen Interfaces abzuleiten, die untereinander keine Verbindung aufweisen. Das ist bei der Verwendung von Klassen nicht erlaubt. Immer, wenn eine derartige Struktur für das Programm notwendig ist, ist es daher sinnvoll, mit Interfaces zu arbeiten.