Interfaces – Verwendung von Schnittstellen in Java

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.

plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

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.

plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

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:

Java Interface

Verwendung von Interfaces in Java – Ausgabe der Beispielanwendung

Mit folgendem Link geht es zurück zum Kurs für Java Anfänger.


Comments 18

  1. Sehr gut Schritt für Schritt erklärt und die Beispiele verdeutlichen die Theorie höchst hilfreich

  2. Pingback: Interfaces – Verwendung von Schnittstellen

  3. Super Erklärung.
    Ich denke in der Testklasse müsste es aber heißen: float gewichtProFlaeche = gewicht/(laenge*breite) oder?

    1. Post
      Author

      Hallo Em,

      ja so ist es. Ich habe den Quellcode entsprechend korrigiert.

      Vielen Dank für den Hinweis!

      Chris

    1. Post
      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

  4. Super gut erklärt. Das Beispiel Programm ist sehr verständlich und übersichtlich programmiert. Herzlichen Dank!

    1. Post
      Author
    1. Post
      Author
  5. Pingback: Interfaces – Verwendung von Schnittstellen – GSA Systems Blog

  6. Pingback: Interfaces (Schnittstellen) in Java

  7. Pingback: Vererbung von Interfaces in Java

  8. Pingback: Mehrfachimplementierung von Interfaces und das Comparable-Interface

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.