Interfaces in Java

Interfaces, oder auf deutsch Schnittstellen, sind auf den ersten Blick gar nicht so leicht zu verstehen. Hat man es aber einmal verstanden, dann ist es gar nicht mehr so schwer; wie so oft im Leben. Von daher, lass uns das Thema angehen.

Du kannst Dir eine Schnittstelle wie eine abstrakte Beschreibung von etwas vorstellen. Oft sind es Eigenschaften, bzw. Fähigkeiten, die beschrieben werden, dies muss aber nicht so sein.

Ein Beispiel

Lass uns schnell eine Schnittstelle Fahrbar zusammen entwerfen; also eine abstrakte Beschreibung der Eigenschaft „fahrbar”. Mögliche Aktionen (in Java sind das Methoden), die dieses „fahrbare Etwas” ausführen kann, wären z.B. beschleunigen, bremsen oder auch lenken.

In Java könnte das dann so aussehen:
public interface Fahrbar {

  public void beschleunigen();

  public void bremsen();

  public void lenken();
}
Mit public interface Fahrbar definieren wir die Schnittstelle, ihre Methoden folgen dann innerhalb der geschweiften Klammern.
Interessant hieran ist, dass die Methoden selbst keinen Rumpf (also keine eigenen geschweiften Klammern) haben, sondern quasi nur genannt werden.

Wie gesagt, alle Methoden des Interfaces haben aber keinen Rumpf, es werden lediglich die möglichen Methoden (=Aktionen) benannt. Daher auch abstrakte Beschreibung; ich sage nur, welche Methoden es gibt, nicht aber was sie bei Aufruf konkret machen.
Das Gegenteil von abstrakten Methoden nennt man übrigens konkrete Methoden. Das sind dann die mit Rumpf.

Der Vollständigkeit halber möchte ich kurz erwähnen, dass nicht alle Methoden eines Interfaces ohne Rumpf sind. Ausnahmen hiervon sind die s.g. default- und static-Methoden.

Verwenden von Interfaces

Wie gesagt, ein Interface entspricht lediglich einer abstrakten Beschreibung. Diese kann man dann, wenn es soweit ist, mit Leben füllen und konkretisieren, oder wie man auch sagt implementieren.

Bezogen auf das obige Beispiel könnten wir z.B. eine Klasse Fahrrad anlegen. Ein Fahrrad stellt definitiv etwas Fahrbares dar und hat auch die Aktionen beschleunigen, bremsen und lenken.
public class Fahrrad implements Fahrbar {

  public void beschleunigen() {
    // trete in die Pedale
  }

  public void bremsen() {
    // betätige den Rücktritt
  }

  public void lenken()
    // benutze den Lenker
  }
}
Ein Auto ist natürlich auch fahrbar, allerdings funktioniert es ganz anders als ein Fahrrad, klar. Daher habe ich -in Pseudocode als Kommentar- die Rümpfe der Methoden anders befüllt.
public class Auto implements Fahrbar {

  public void beschleunigen() {
    // trete das Gaspedal
  }

  public void bremsen() {
    // trete das Bremspedal
  }

  public void lenken()
    // verwende das Lankrad
  }
}

Warum gibt es Schnittstellen?

Schnittstellen haben gleich mehrere Vorteile.

Zum einen erlauben sie, den Code abstrakt zu halten, so dass dieser erst später konkretisiert werden muss. Es reicht, wenn man während der Entwicklung nur die Schnittstelle und deren abstrakten Methoden kennt.

Zum anderen gilt, dass ein Interface quasi eine gemeinsame Schnittmenge darstellt. Alle Klassen, die das Interface implementieren, haben automatisch auch seine Methoden.
Man kann im Nachhinein noch Klassen hinzufügen, die das Interface implementieren, ohne dafür anderen Quelltext anfassen zu müssen.

Noch ein Beispiel

Lass uns noch schnell ein kleines weiteres Beispiel machen, um das zu demonstrieren.

Nehmen wir an, Du möchtest ein Programm schreiben, das PDFs und JPGs drucken kann.

Das könnte dann so aussehen

public class MyDruckprogramm {

  public void print(Pdf pdf) {
    pdf.print();
  }

  public void print(Jpg jpg) {
    jpg.print();
  }
}

(Die Methoden dürfen übrigens beide print heißen. Das ist erlaubt, da die Parameter (pdf und jpg) unterschiedliche Datentypen sind. Das nennt man dann Methoden-Überladung, oder Überladen von Methoden.)

Was ist aber, wenn nun auch noch GIFs gedruckt werden sollen? Dann muss im Anschluss dessen noch eine weitere print-Methode hinzugefügt werden:

public class MyDruckprogramm {

  public void print(Pdf pdf) {
    pdf.print();
  }

  public void print(Jpg jpg) {
    jpg.print();
  }

  public void print (Gif gif) {
    gif.print();
  }
}

Du merkst wahrscheinlich auch, dass alle Methoden ungefähr das gleiche machen und es auch irgendwie unpraktisch ist, für jeden weiteren Datentypen, den ich ausdrucken lassen möchte, eine eigene print-Methode schreiben zu müssen.

Wie können wir das jetzt eleganter machen?

Lass uns doch einfach ein Interface Printable definieren, das nur eine Methode print hat:

public interface Printable {
  public void print();
}

Nun müssen all die oben genannten Klassen (Pdf, Jpg, Gif, usw.) das Interface auch implementieren.

public class Pdf implements Printable {
  public void print() {
    // hier ist der Code, der das PDF ausdruckt
  }
}


public class Jpg implements Printable {
  public void print() {
    // hier ist der Code, der das JPG druckt
  }
}

usw.

Die Klasse MyDruckprogramm von oben lässt sich nun, nach dem Einführen der Schnittstelle Printable, deutlich eleganter gestalten:

public class MyDruckprogramm {
  public void print(Printable printable) {
    printable.print();
  }
}

Da die Klassen Pdf, Jpg & Co. alle vom Typ Printable sind (sie implemntieren ihn ja), können sie auch der print-Methode übergeben werden.

  new MyDruckprogramm().print(new Pdf());
  new MyDruckprogramm().print(new Jpg());
  // usw.

Der große Vorteil ist nun die völlig unkomplizierte Erweiterbarkeit!

Kommt nun noch ein weiterer Datentyp hinzu, der auch druckbar sein soll, z.B. eine Klasse Html, muss diese lediglich das Printable-Interface implementieren.
Im Anschluss dessen kann MyDruckprogramm.print(...) auch mit einer Instanz dieser neu eingeführten Klasse aufgerufen werden, ohne dass irgendwas zu ändern ist.

Praktisch, oder?
Möchtest Du noch mehr über Schnittstellen in Java kennenlernen?
Created with