In Java kann eine abgeleitete Klasse nur genau eine Vaterklasse haben.
Das Erben von mehr als einer Superklasse (Mehrfachvererbung) ist nicht möglich.
Den Entwicklern der Programmiersprache Java war Mehrfachvererbung bekannt und auch die Nachteile die sie mit sich bringt. Ganz auf Mehrfachvererbung verzichten wollten sie aber nicht und schafften einen Mittelweg: das Interface.
Interfaces können als Ersatzkonstrukt für Mehrfachvererbung gesehen werden. Eine Klasse kann mehrere Interfaces implementieren, d.h. ihr können mehrere Schnittstellen zur Verfügung gestellt werden. Jede dieser Schnittstellen (Interfaces) muss aber von der Klasse vollständig implementiert werden.
Was ist ein Interface in Java?
Ein Interface ist eine Schnittstelle, über die einer Klasse bestimmte Funktionen zur Verfügung gestellt werden. Um die Funktionen nutzen zu können, müssen sie aber erst von der Klasse implementiert werden. Das Interface gibt nur den Rahmen (die Methodendeklarationen) vor.
Interfaces können als eine besondere Form einer Klasse angesehen werden. Sie enthalten ausschließlich Konstanten und abstrakte Methoden. Die abstrakten Methoden müssen von der Klasse implementiert werden, der das Interface zugewiesen wird.
Die Deklaration von Interfaces ist einfach, es muss nur das Schlüsselwort class durch interface ersetzt werden.
Die Methoden in einem Interface sind implizit public (öffentlich) und abstract. Variablen und Konstruktoren sind nicht erlaubt, Konstanten können jedoch in Interfaces definiert werden.
Wie werden Interfaces in Java verwendet
Um den Nutzen von Interfaces zu verdeutlichen, möchten wir eine Anwendung programmieren, die das Interface Transportierbar verwendet.
Der folgende Quellcode definiert das Transportierbar-Interface:
Beispielanwendung: Das Interface Transportierbar
/* * Beispielanwendung: das Interface Transportierbar */ public interface Transportierbar { public final float MAX_GEWICHT_PRO_FLAECHE = 29.99F; // hier geht auch: float MAX_GEWICHT_PRO_FLAECHE = 29.99F; // alle Methoden in Interfaces sind implizit public und abstract // alle vier Methodendeklarationen sind zulässig // jede dieser Methoden ist public und abstract float gewicht(); abstract float laenge(); public float breite(); public abstract float hoehe(); boolean zerbrechlich(); String beschriftung(); }
Unser oben definiertes Interface Transportierbar ähnelt einer abstrakten Klasse. Wir wollen es als Schnittstelle nutzen, über die auf wichtige Transportdetails von realen Objekten zugegriffen werden kann. Dazu müssen die im Interface deklarierten abstrakten Methoden von den Klassen implementiert werden, da das Interface die Funktionalität lediglich beschreibt, aber nicht umsetzt (implementiert).
Alle Klassen die dieses Interface nutzen wollen, müssen es vollständig implementieren. Dazu wird an dem Namen der jeweiligen Klasse das implements-Schlüsselwort und der Name des zu implementierenden Interface angefügt.
Werden nicht alle, von dem Interface deklarierten Methoden, implementiert, liefert der Java-Compiler eine Fehlermeldung zurück. Es ist aber möglich diese zu umgehen, dazu muss die Klasse als abstrakt deklariert werden. Dann können aber auch keine Objekte von dieser abstrakten Klasse instanziert werden.
In dem folgenden Quellcode wollen wir nun unser Transportierbar-Interface mit Hilfe der Klasse Tisch implementieren:
Beispielanwendung: Die Tisch-Klasse implementiert das Interface
/* * Beispielanwendung: die Tisch-Klasse implementiert das Interface */ public class Tisch implements Transportierbar { public String kennzeichnung; public boolean zerbrechlich; public float gewicht, laenge, hoehe, breite; public Tisch (String name, boolean zerbrechlich, float gewicht, float laenge, float breite, float hoehe) { kennzeichnung = name; this.zerbrechlich = zerbrechlich; this.gewicht = gewicht; this.laenge = laenge; this.breite = breite; this.hoehe = hoehe; } public float gewicht() { return gewicht; } public float laenge() { return laenge; } public float breite() { return breite; } public float hoehe() { return hoehe; } public boolean zerbrechlich() { return zerbrechlich; } public String beschriftung() { String text = "Tisch " + kennzeichnung; return text; } }
In dem oberen Quellcode werden alle (abstrakten) Methoden des Interfaces Transportierbar von der Klasse Tisch implementiert, somit ist es möglich Tisch-Objekte zu instanzieren (konkret werden zu lassen).
Ein Interface ist genau dann besonders sinnvoll, wenn es Eigenschaften einer Klasse beschreibt (vorgibt), die nicht direkt in der Vererbungshierachie abgebildet werden können.
Um dieses Merkmal bei unserem Interface Transportierbar optimal zu nutzen, werden wir eine zweite Klasse definieren, welche nicht in die Vererbungshierarchie der Klasse Tisch passt.
Die folgende Klasse Schaf steht der Klasse Tisch aus Vererbungssicht nicht sehr nahe, daher würden sich beide Klassen nur umständlich in einer gemeinsamen Vererbungshierarchie abbilden lassen. Da die Klasse Schaf aber auch über bestimmte Eigenschaften der Klasse Tisch verfügen soll, ist hier die Verwendung eines gemeinsamen Interfaces der richtige Weg.
Das Interface Transportierbar beschreibt dabei für beide implementierenden Klassen die wichtigen Transportdetails, die natürlich noch in jeder der beiden Klassen entsprechend implementiert werden müssen. Das Interface Transportierbar liefert nur die Methodendeklarationen als gemeinsame Schnittstelle.
In dem folgenden Quellcode wollen wir nun unser Transportierbar-Interface für die Klasse Schaf implementieren.
Beispielanwendung: Die Schaf-Klasse implementiert das Interface
/* * Beispielanwendung: die Schaf-Klasse implementiert das Interface */ public class Schaf implements Transportierbar { public String name; public boolean zerbrechlich; public float gewicht, laenge, hoehe, breite; public Schaf (String name, boolean zerbrechlich, float gewicht, float laenge, float breite, float hoehe) { this.name = name; this.zerbrechlich = zerbrechlich; this.gewicht = gewicht; this.laenge = laenge; this.breite = breite; this.hoehe = hoehe; } public float gewicht() { return gewicht; } public float laenge() { return laenge; } public float breite() { return breite; } public float hoehe() { return hoehe; } public boolean zerbrechlich() { return zerbrechlich; } public String beschriftung() { String text = "Lebewesen: Schaf " + name; return text; } }
Jetzt haben wir unser Interface Transportierbar für die beiden Klassen Tisch und Schaf implementiert. Dadurch sind die für den Transport wichtigen Methoden gewicht, laenge, breite, hoehe, zerbrechlich und beschriftung für beide Klassen jeweils in der passenden Implementierung verfügbar und das unabhängig von der jeweiligen Vererbungshierarchie.
Um das Interface zu testen, benötigen wir noch eine Testklasse. Diese wollen wir als nächstes definieren:
Beispielanwendung: Die Testklasse für das Interface
/* * Beispielanwendung: die Testklasse für das Interface */ public class InterfaceTest { public static boolean transportMachbar (Transportierbar tDetails) { float gewicht = tDetails.gewicht(); float laenge = tDetails.laenge(); float breite = tDetails.breite(); float gewichtProFlaeche = gewicht/(laenge*breite); if (gewichtProFlaeche < tDetails.MAX_GEWICHT_PRO_FLAECHE) { return true; } return false; } public static float berechneVolumen (Transportierbar tDetails) { float volumen = tDetails.laenge()*tDetails.breite()*tDetails.hoehe(); return volumen; } public static String erstelleBeschriftung (Transportierbar tDetails) { String text = tDetails.beschriftung(); if (tDetails.zerbrechlich()) { return "-Zerbrechlich- " + text; } return text; } public static void main (String[] args) { // erzeugen eines Tisch- und Schaf-Objekts Tisch myTisch = new Tisch ("2014.AE Esstisch", false, 27.3F, 3.0F, 2.2F, 1.3F); Schaf mySchaf = new Schaf ("Cloud", true, 42.9F, 1.1F, 0.82F, 0.55F); System.out.println("\nTransportdetails fuer den Tisch:\n"); System.out.println("Volumen: " + berechneVolumen(myTisch) + " m^3"); System.out.println("Verpackungsaufdruck: " + erstelleBeschriftung(myTisch)); System.out.println("Transport machbar: " + transportMachbar(myTisch)); System.out.println("\n\nTransportdetails fuer das Schaf:\n"); System.out.println("Volumen: " + berechneVolumen(mySchaf) + " m^3"); System.out.println("Verpackungsaufdruck: " + erstelleBeschriftung(mySchaf)); System.out.println("Transport machbar: " + transportMachbar(mySchaf)); } }
Unsere Testanwendung erzeugt zu Beginn zwei Objekte (myTisch und mySchaf), die beide unser Transportierbar-Interface implementieren. Daher können beide Objekte an die Methoden berechneVolumen, erstelleBeschriftung und transportMachbar übergeben werden, obwohl sie Instanzen verschiedener Klassen sind.
Dies ist möglich, da eine Interface-Variable kompatibel zu allen Objekten ist, deren Klassen dieses Interface implementieren.
Es muss aber das Objekt an die Methode als Interface-Variable übergeben werden, so wie es bei der Methode berechneVolumen der Fall ist. Die Methode erwartet als Argument ein Objekt vom Typ Transportierbar, somit können ihr alle Objekte übergeben werden, deren Klassen das Interface Transportierbar implementieren. In unserem Fall sind das Objekte vom Typ Tisch und Schaf.
Da an die Methode berechneVolumen nur Objekte des Typs Transportierbar übergeben werden können, ist sichergestellt, dass die übergebenen Objekte auch alle Methoden des Interfaces Transportierbar implementiert haben.
Hinweis: Hierbei ist schön zu erkennen, dass durch das Interface ein neuer Datentyp entstanden ist, nämlich der Typ Transportierbar. Alle Objekte, deren Klassen dieses Interface implementieren, sind somit sowohl vom Typ ihrer eigenen Klasse als auch vom Typ des Interfaces.
Auf Interface-Namen kann auch der instanceof-Operator angewendet werden. So liefern die folgenden Anweisungen myTisch instanceof Transportierbar
und myTisch instanceof Tisch
beide den Wert true zurück.
Starten wir nun die Beispielanwendung. In der unteren Abbildung ist die Kommandozeilenausgabe des Programms dargestellt:
Mit folgendem Link geht es zurück zum Kurs für Java Anfänger.
Comments 18
Sehr gut erklärt. Dankesehr
Sehr gut Schritt für Schritt erklärt und die Beispiele verdeutlichen die Theorie höchst hilfreich
Super, endlich mal eine Erklärung ohne viel bla bla drum rum.
DANKE
Pingback: Interfaces – Verwendung von Schnittstellen
Gut erklärt! Kann mich den anderen nur anschließen. 🙂
Super Erklärung.
Ich denke in der Testklasse müsste es aber heißen: float gewichtProFlaeche = gewicht/(laenge*breite) oder?
Author
Hallo Em,
ja so ist es. Ich habe den Quellcode entsprechend korrigiert.
Vielen Dank für den Hinweis!
Chris
Eine sehr, wirklich sehr gute Erklärung. Danke dir!
Author
Hallo Alexander,
danke für’s Lob! Interfaces in Java zu nutzen, ist oft einfacher als man denkt. Ich habe eine gewisse Zeit gebraucht, bis ich verstanden hatte, wofür sie eigentlich gedacht sind.
Viele Grüße, Chris
Super gut erklärt. Das Beispiel Programm ist sehr verständlich und übersichtlich programmiert. Herzlichen Dank!
Author
Hallo Anonym,
vielen Dank für das positive Feedback.
Viele Grüße, Chris
Danke, hat geholfen !
Besser erklärt als sonst überall, danke! 🙂
Author
Hi Alen,
vielen Dank für das große Lob!!!
Viele Grüße, Chris
Pingback: Interfaces – Verwendung von Schnittstellen – GSA Systems Blog
Pingback: Interfaces (Schnittstellen) in Java
Pingback: Vererbung von Interfaces in Java
Pingback: Mehrfachimplementierung von Interfaces und das Comparable-Interface