04.08 Interfaces (vs. Mehrfachvererbung)

Manche Programmiersprachen (C++, Perl, Python, …) bieten die Möglichkeit der Mehrfachvererbung von Klassen. Andere (Java, C#, Delphi, …) beschränken sich lieber auf das Implementieren von Schnittstelle in Form von Interfaces. Mit Interfaces ist es in Java möglich eine Art der Mehrfachvererbung nachzubilden. Doch was sind Interfaces eigentlich genau? Und worin liegt der Unterschied zur konventionellen Mehrfachvererbung von Klassen? Dieses Kapitel hilft Ihnen dabei, die passenden Antworten auf diese Fragen zu finden.

Interfaces

Wenn Sie mehrere – möglicherweise vollkommen unterschiedliche – Klassen zusammen verarbeiten möchten, dann haben diese wenigstens einen Punkt gemeinsam. Dieser Punkt stellt dann die Schnittstelle (Interface) zu den anderen Klassen dar. Z. B. sind die Stoffe Holz, Gas und Öl alle brennbar, obwohl sie sonst nicht viel gemeinsam haben. Die Schnittstelle zwischen diesen Klassen wäre folglich brennbar, natürlich können auch alle brennbaren Elemente angebrannt werden. Wie Sie sich sicherlich bereits gedacht haben, werden für solche Aufgaben Interfaces verwendet. Die Deklaration eines Interfaces unterscheidet sich nur geringfügig von Klassen:

  • Anderer Kopf
  • Methoden bleiben leer und dürfen nur gewisse Modifizierer enthalten
  • Variablen dürfen ebenfalls nicht mit allen Modifizierern deklariert werden

Um ein Interface zu definieren, wird ähnlich vorgegangen, wie wenn Sie eine neue Klasse anlegen möchten. Sie vertauschen lediglich das Schlüsselwort class im Kopf mit dem Schlüsselwort interface.

public interface MyInterface {}

Die Methoden eines Interfaces haben niemals einen Körper, werden also mit einem Semikolon abgeschlossen. Deshalb sind Klassen, die ein Interface implementieren, auch verpflichtet alle vorgeschriebenen Methoden dieses Interfaces zu implementieren. Die Methoden können dabei einen beliebigen Rückgabewert und Übergabeparameter haben, allerdings müssen sie public und abstract sein.

public interface MyInterface {
  public abstract void doSomethingCool();
}

Anders sieht es bei der Verwendung von Variablen aus. Diese müssen nicht nur public sein, sondern zusätzlich auch static und final. Somit ist es u. a. möglich in Interfaces statische Konstanten anzulegen. Beachten Sie hierbei aber die Einführung von Enumerations seit Java 1.5.

public interface MyInterface {
  public static final int KONSTANTE = 1;
  public abstract void doSomethingCool();
}

Dadurch, dass Attribute in Interfaces immer public static final, und Methoden public abstract sind, erlaubt es der Compiler, dass diese Modifizierer nicht, oder nur teilweise benannt werden müssen. Das hier

public interface MyInterface {
  public static final int KONSTANTE = 1;
  public abstract void doSomethingCool();
}

ist also äquivalent zu dem hier

public interface MyInterface {
  int KONSTANTE = 1;
  public void doSomethingCool();
}

und auch zu dem hier

public interface MyInterface {
  public static int KONSTANTE = 1;
  void doSomethingCool();
}

Beim Design sollten Sie lediglich darauf achten, dass Sie einheitlich vorgehen. Manche Entwickler sehen es jedoch als unnötig und doppelt an, wenn Sie die Modifizierer in Interfaces explizit benennen. Sie bevorzugen folglich eine solche Schreibweise:

public interface MyInterface {
  int KONSTANTE = 1;
  void doSomethingCool();
}

Für Anfänger ist es aber vielleicht günstiger und leichter zu merken, wenn die Modifizierer ausgeschrieben werden.

Der Zugriff auf Attribute und Methoden in Interfaces unterscheidet sich natürlich nicht von den Zugriffen in Klassen.

Wenn Sie ein Interface verwenden möchten, müssen Sie entweder eine Innere Klasse erstellen, was Sie aber zu einem späteren Zeitpunkt lernen werden, oder aber – und das ist der normale Weg – es von einer Klasse implementieren lassen. Dies geschieht mit dem Schlüsselwort implements im Kopf der Klasse nach dem Klassennamen bzw. (falls vorhanden) nach dem Namen der beerbten Klasse.

public class InterfaceImplementingClass implements MyInterface {

Falls Sie eine Klasse mehrere Interfaces implementieren lassen möchten, können Sie diese über Kommata separieren.

public class InterfaceImplementingClass implements MyInterface, MyInterface2, MyInterface3 {

Diese Klasse muss selbstverständlich alle vorgegebenen Methoden des Interfaces implementieren. Geschieht dies nicht, wird beim Kompilieren ein entsprechender Fehler vom Compiler gemeldet.

Die fertige Klasse könnte z. B. so aussehen:

public class InterfaceImplementingClass implements MyInterface {

  public void doSomethingCool() {
    System.out.println("Konstante: " + KONSTANTE);
  }
}

Sie sehen also, dass die implementierende Klasse auch auf die Variablen des Interfaces zugreifen kann. Ebenso könnten Sie in diesem Fall auch von einer weiteren Klasse auf die Konstante des Interfaces über die Klasse InterfaceImplementingClass zugreifen.

public class AnOtherClass {
  
  public static void main(String[] args) {
    System.out.println(InterfaceImplementingClass.KONSTANTE);
  }
}

Sollte eine Klasse, die ein Interface implementiert, beerbt werden, so implementiert die Kindklasse automatisch auch alle Interfaces der Elternklasse. Aber auch Interfaces können einander beerben. Hierzu wird wie gewohnt das Schlüsselwort extends verwendet. Eine weitere Besonderheit an Interfaces ist, dass diese von mehreren anderen Interfaces erben können.

public interface MyInterface extends AnOtherInterface {}
public interface ThirdInterface extends FirstInterface, SecondInterface {}

Dabei muss dann die implementierende Klasse selbstverständlich die Methoden von beiden Interfaces beinhalten.

Da dieses Kapitel bis jetzt sehr abstrakt und für die meisten Programmierneulinge wohl nur schwer nachvollziehbar erscheint, versuchen wir uns nun an einem praktischem Beispiel.

Stellen Sie sich vor, Ihre Heizanlage zuhause kann aus den unterschiedlichsten Stoffen Wärme gewinnen. Alles was verheizbar ist kann Ihre Heizanlage verbrennen. Dadurch wird eine, je nach Stoff unterschiedliche, Menge an Energie gewonnen. Die Stoffe, die Sie verheizen wollen, müssen folglich allesamt verheizbar sein und beim Verbrennen Wärme erzeugen. Aus dieser Aufgabenstellung lässt sich ohne Probleme ein entsprechendes Interface ableiten:

public interface Verheizbar {
  // verbrennt den brennbaren Stoff und liefert die Stunden zurück,
  // wie lange die Wohnung mit diesem Stoff geheizt werden kann
  int verbrennen();
}

Lassen Sie uns nun die Heizanlage darstellen. Dieser bietet die Möglichkeit verheizbare Stoffe zu verbrennen, und speichert die gewonnene Wärme in einem separaten Bereich. Bei Bedarf kann die Wärme dann abgegeben werden.

public class Heizanlage {

  private int waermeSpeicher = 0;
	
  public void speicherAuffuellen(Verheizbar stoff) {
    this.waermeSpeicher += stoff.verbrennen();
  }

  // gibt false zurück, falls der Speicher leer ist
  public boolean wohnungHeizen(int zeit) {
	
    if (this.waermeSpeicher - zeit < 0) {
      System.out.println("Es kann nur noch für " + (this.waermeSpeicher) + " Stunden geheizt werden");
      this.waermeSpeicher = 0;
    }
    else  {
      this.waermeSpeicher -= zeit;
    }
    if (this.waermeSpeicher == 0) {
      System.out.println("Bitte Speicher auffüllen!");
      return false;
    }
    return true;
  }
}&#91;/sourcecode&#93;

Letztendlich fehlen noch die verheizbaren Stoffe. Hierzu wählen wir Holz, Öl und Gas. Für jeden Stoff wird eine neue Klasse erstellt, die das Interface <code>Verheizbar</code> implementiert und die Methode <code>verbrennen</code> auf ihre eigene Art mit Inhalt füllt.

public class Holz implements Verheizbar {

  public int verbrennen() {
    return 2;
  }
}
public class Oel implements Verheizbar {

  public int verbrennen() {
    return 1;
  }
}
public class Gas implements Verheizbar {

  public int verbrennen() {
    return 3;
  }
}

Jetzt müssen Sie nur noch ihre Heizanlage mit verheizbaren Stoffen füllen und den Tag über heizen:

public static void main(String[] args) {

Heizanlage anlage = new Heizanlage();
// Heizanlage auffüllen
anlage.speicherAuffuellen(new Holz());
anlage.speicherAuffuellen(new Gas());
anlage.speicherAuffuellen(new Holz());
anlage.speicherAuffuellen(new Oel());
anlage.speicherAuffuellen(new Holz());
// Vorrat anlegen
Verheizbar[] vorrat = new Verheizbar[8];
vorrat[0] = new Holz();
vorrat[1] = new Oel();
vorrat[2] = new Holz();
vorrat[3] = new Gas();
vorrat[4] = new Gas();
vorrat[5] = new Holz();
vorrat[6] = new Oel();
vorrat[7] = new Holz();
// Tag mit 24 Stunden
for (int i = 0; i < 24; i++) { if (!anlage.wohnungHeizen(1)) { for (int j = 0; j < vorrat.length; j++) { if (vorrat[j] != null) { anlage.speicherAuffuellen(vorrat[j]); vorrat[j] = null; break; } } } } }[/sourcecode] Sie sehen, dass durch die Implementierung des Interfaces Verheizbar die Klassen Holz, Oel und Gas identisch behandelt werden können, obwohl Sie ansonsten nichts gemeinsam haben. Haben Sie die Ähnlichkeit zur Vererbung bemerkt?

Mehrfachvererbung

In den letzten Kapiteln haben Sie bereits gelernt, was Vererbung ist und für was sie benötigt wird. Mit diesem Vorwissen lässt sich der Begriff „Mehrfachvererbung“ auch relativ einfach erklären. Wie der Name schon vermuten lässt, erbt eine Klasse bei der Mehrfachvererbung nicht etwa von einer einzigen Klasse, sondern von mehreren. Angenommen wir haben eine Klasse Fahrzeug. Von dieser Klasse erben die beiden Klassen Landfahrzeug (Auto) und Wasserfahrzeug (Boot). Jetzt möchten Sie ein Fahrzeug erzeugen, welches sich sowohl zu Land, als auch im Wasser fortbewegen kann. Hierzu lassen Sie eine weitere Klasse von Landfahrzeug und von Wasserfahrzeug erben. Schon haben Sie ein Amphibienfahrzeug, welches fahren und schwimmen kann – und gleichzeitig ein Beispiel für die (in Java nicht mögliche) Mehrfachvererbung.

Dabei müssen Sie ganz grundsätzlich in der Definition unterscheiden. Mehrfachvererbung vererbt Schnittstellen und Implementierungen, Interfaces vererben auch, aber nur Schnittstellen und keine Implementierungen. Wenn in Java also eine Klasse mehrere Schnittstellen implementieren soll, muss dies über Interfaces realisiert werden und somit auf vordefinierten Code verzichtet werden generic viagra from india. Sie können allerdings auch von einer Klasse erben und zeitgleich mehrere Schnittstellen implementieren, so dass Sie zumindest ein wenig Code vorgeben können.

Vorteile Mehrfachvererbung

– Leichtes Zusammenfügen von zwei unabhängigen Klassen
– Erhöhte Wiederverwendbarkeit der Klassen

Nachteile Mehrfachvererbung

– Design ist meist kompliziert und undurchsichtig
– Namenskonflikte
– wiederholte Vererbung
– Diamond-Problem

Man spricht vom Diamond-Problem, wenn eine Klasse von mehreren anderen Klassen erbt, bei denen mindestens zwei Klassen von der selben Superklasse abstammen (gemeinsamer Vorfahre). Dadurch wird unklar, welche Methoden-Implementierungen und Werte von dem gemeinsamen Vorfahren geerbt werden sollen.

Vorteile Interfaces

– Einheitliche Behandlung von unterschiedlichen Klassen
– Umgehen von vielen Problemen der Mehrfachvererbung in C++

Nachteile Interfaces

– Keine vordefinierten Methoden-Inhalte
– Problematische Situation, wenn zwei Interfaces implementiert werden, die beide min. eine identische Methode vorschreiben.

Mehrfachvererbung vs. Interfaces

Als Fazit kann man sagen, dass sich Interfaces sauberer implementieren lassen als Mehrfachvererbung. Sie haben nur einen Nachteil gegenüber der Mehrfachvererbung: Es ist nicht möglich einen Methoden-Inhalt vorzuschlagen. Das kann Ihnen als Java-Programmierer aber egal sein, da Sie ohnehin keine andere Wahl haben, als auf Interfaces zurückzugreifen.

4 Replies to “04.08 Interfaces (vs. Mehrfachvererbung)”

  1. Cyrill Brunner

    Ich möchte ein Objekt bzw. die Klasse davon auf die vorhandenen Interfaces überprüfen. Schön und gut, die List bekommt man ja mit
    obj.getClass().getInterfaces(); und wird dann in einem Class-Array gespeichert.
    Wie gesagt, alles gemacht. Jetzt kommt aber das Problem, wie ich überprüfen will, welche Interfaces es sind. Denn mit instanceof geht es nicht, da Class und ein Interface inkompatibel sind.

    Hat jemand eine Ahnung, wie man die Interfaces überprüfen kann?

  2. Stefan Kiesel

    Hallo Cyrill,

    ein Beispiel:

    ArrayList<String> list = new ArrayList<String>();
    Class< ?>[] interfaces = list.getClass().getInterfaces();
    for (Class< ?> i : interfaces) {
    	if (i.isAssignableFrom(java.util.List.class)) {
    		System.out.println("ArrayList implementiert List");
    	}
    }
    // oder einfach
    if (list instanceof java.util.List) {
    	System.out.println("ArrayList implementiert List");
    }

    Grüße
    Stefan

  3. Matu

    Hallo Stefan,

    lese mich gerade wieder in Java ein und bin über g**gle hier gelandet … Danke für die guten und verständlichen Erklärungen!

    Kleine Anmerkung (wenn ich darf):
    Müßte in dem Beispiel oben die Ausgabe => System.out.println(„Es kann nur noch für “ + (this.waermeSpeicher) + “ Stunden geheizt werden“); <= nicht im folgenden else-Block stehen, da diese sonst nie gezeigt würde (oder if (this.waermeSpeicher – zeit == 0)?

    Viele Grüße
    Matu

  4. Stefan Kiesel

    Hallo Matu,

    die Ausgabe dient dafür, dass der „User“ eine Rückmeldung bekommt wenn er nicht mehr so lange heizen kann wie er gerne möchte. In diesem Fall wird das noch verfügbare Material verheizt aber nicht länger geheizt. Soweit also alles korrekt.

    Grüße
    Stefan

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.