W3docs

Java Interfaces

Verträge in Java mit Interfaces definieren — abstrakte Methoden, Default-Methoden und Mehrfachvererbung von Typen.

Ein Interface ist ein Vertrag: eine benannte Menge von Operationen, die jede implementierende Klasse bereitzustellen verspricht. Interfaces haben keinen Instanzzustand, keine Konstruktoren und (mit einer Ausnahme, die im nächsten Kapitel behandelt wird) keine Methodenrümpfe. Sie beschreiben was ein Typ tun kann und überlassen jedes wie den Implementierungen.

Interfaces sind Javas Antwort auf Mehrfachvererbung. Eine Klasse erbt von genau einer Klasse, kann aber beliebig viele Interfaces implementieren — so lassen sich Comparable, AutoCloseable und Iterable ohne Mehrdeutigkeit auf demselben Typ kombinieren.

Ein Interface deklarieren

Verwende interface statt class:

public interface Shape {
  double area();              // implicitly public abstract
  double perimeter();
}

Methodendeklarationen in einem Interface sind implizit public abstract. Man kann diese Modifikatoren hinschreiben, aber die meisten Styleguides lassen sie weg, da sie redundant sind.

Ein Interface implementieren

Eine Klasse gibt dies mit implements an. Die Klasse muss für jede Methode, die das Interface deklariert, einen Rumpf bereitstellen:

public class Circle implements Shape {
  private final double r;
  public Circle(double r) { this.r = r; }

  @Override public double area()      { return Math.PI * r * r; }
  @Override public double perimeter() { return 2 * Math.PI * r; }
}

Wenn eine Klasse ein Interface implementiert, aber nicht alle Methoden bereitstellt, muss die Klasse als abstract deklariert werden — dieselbe Regel wie bei abstrakten Klassen.

Es lassen sich auch mehrere Interfaces gleichzeitig implementieren:

public class Money implements Comparable<Money>, java.io.Serializable {
  private final long cents;
  public Money(long cents) { this.cents = cents; }
  public int compareTo(Money other) { return Long.compare(this.cents, other.cents); }
}

Das ist „Mehrfachvererbung von Typen" — Money ist sowohl ein Comparable<Money> als auch ein Serializable. Code, der eines der beiden erwartet, kann ein Money verwenden.

Gegen ein Interface programmieren

Der Zweck von Interfaces besteht darin, Code gegen den Vertrag zu schreiben, nicht gegen die Implementierung:

public double sumAreas(List<Shape> shapes) {
  double sum = 0;
  for (Shape s : shapes) sum += s.area();
  return sum;
}

sumAreas kennt Circle nicht und muss es nicht kennen. Fügt man Square implements Shape und Triangle implements Shape hinzu, funktioniert die Funktion auch mit Listen davon — ohne Änderungen.

Die Standardbibliothek basiert auf diesem Muster. Man deklariert eine Variable fast immer mit einem Interface-Typ und instanziiert eine konkrete Implementierung:

List<String>   names = new ArrayList<>();        // List, not ArrayList
Map<String, Integer> counts = new HashMap<>();   // Map,  not HashMap

Wechselt man später zu LinkedList oder LinkedHashMap, ändert sich nur die new-Zeile.

Konstanten auf Interfaces

Jedes in einem Interface deklarierte Feld ist implizit public static final — eine Konstante. Normalerweise fügt man Interfaces keine Konstanten hinzu (das gilt als schlechter Stil — das Constant Interface Antipattern), aber die Syntax existiert:

public interface Color {
  String DEFAULT = "black";    // implicitly public static final
}

Wenn Konstanten benötigt werden, ist ein enum oder eine einfache final class mit public static final-Feldern vorzuziehen.

Interfaces können Interfaces erweitern

Ein Interface kann andere Interfaces erweitern — sogar mehrere gleichzeitig:

public interface Readable    { String read(); }
public interface Writable    { void write(String s); }
public interface ReadWrite extends Readable, Writable { }

Jede Klasse, die ReadWrite implementiert, muss nun sowohl read() als auch write() bereitstellen. Es gibt hier keine Unterscheidung zwischen class extends und interface implements — Interfaces erweitern Interfaces einfach mit extends.

Default- und statische Methoden (Vorschau)

Seit Java 8 können Interfaces default-Methoden (Rümpfe, die über das Schlüsselwort default bereitgestellt werden) und static-Methoden haben. Sie ermöglichen es, einem Interface Verhalten hinzuzufügen, ohne jede bestehende Implementierung zu brechen. Die vollständige Behandlung folgt im nächsten Kapitel, Default-Methoden:

public interface Shape {
  double area();

  // Default method — implementors get this for free.
  default String describe() {
    return getClass().getSimpleName() + " area=" + area();
  }

  // Static factory on the interface itself.
  static Shape unitCircle() { return new Circle(1); }
}

Marker-Interfaces

Ein Marker-Interface deklariert keine Methoden. Es dient ausschließlich als Label, auf das Laufzeitcode prüfen kann:

public interface Cacheable { }      // no methods

public class Snapshot implements Cacheable { ... }

Dann irgendwo: if (obj instanceof Cacheable) { ... }. Modernes Java bevorzugt Annotationen für diese Art von Metadaten (@Cacheable statt implements Cacheable), aber Serializable, Cloneable und RandomAccess sind bekannte Marker-Interfaces in der Standardbibliothek.

Funktionale Interfaces

Ein Interface mit genau einer abstrakten Methode ist ein funktionales Interface. Das ist wichtig, weil Java es erlaubt, ein solches Interface mit einem Lambda-Ausdruck oder einer Methodenreferenz statt einer vollständigen anonymen Klasse zu implementieren — das Lambda ist die Implementierung dieser einzelnen Methode.

@FunctionalInterface
public interface Transformer {
  String apply(String input);     // the single abstract method
}

Transformer upper = s -> s.toUpperCase();   // lambda implements apply
System.out.println(upper.apply("hi"));      // prints: HI

Die optionale Annotation @FunctionalInterface ist eine Compile-Zeit-Absicherung: Der Code kompiliert nicht, wenn das Interface am Ende mehr als eine abstrakte Methode hat. (Default- und statische Methoden zählen nicht zum Limit.) Die Standardbibliothek liefert eine ganze Werkzeugkiste davon — Runnable, Comparator, Function, Predicate, Supplier — behandelt in funktionale Interfaces.

Wahl zwischen Interface und abstrakter Klasse

Der Entscheidungsbaum:

  1. Müssen Unterklassen Zustand oder gemeinsame Methodenimplementierungen teilen? Wenn ja, ist wahrscheinlich eine abstrakte Klasse die richtige Wahl — Interfaces können keinen Instanzzustand halten.
  2. Soll ein Typ von mehreren ansonsten unverwandten Klassen erfüllt werden? Wenn ja, ein Interface — eine Klasse kann viele Interfaces implementieren, aber nur eine Klasse erweitern.
  3. Wird der Vertrag im Laufe der Zeit wachsen? Interfaces entwickeln sich vorsichtiger — das Hinzufügen einer abstrakten Methode zu einem Interface bricht jede Implementierung, es sei denn, man macht sie zu einer default-Methode. Abstrakte Klassen können eine konkrete Methode hinzufügen, ohne etwas zu brechen.

In den meisten realen Codebasen gewinnen Interfaces bei Typverträgen, während abstrakte Klassen im Hintergrund auftauchen, wenn mehrere Implementierungen Infrastruktur teilen.

Ein ausgearbeitetes Beispiel

java— editable, runs on the server

Was kommt als Nächstes

Interfaces waren früher reine Verträge — ohne Methodenrümpfe. Seit Java 8 wurde das gelockert: Interfaces können Default-Methoden, static-Methoden und sogar private Hilfsmethoden bereitstellen. Das nächste Kapitel bietet eine Übersicht dieser Ergänzungen. Weiter zu Default-Methoden.

Übungen

Übung
Was ist der praktische Grund dafür, dass Java einer Klasse nicht erlaubt, mehr als eine Klasse zu erweitern, aber das Implementieren vieler Interfaces erlaubt?
Was ist der praktische Grund dafür, dass Java einer Klasse nicht erlaubt, mehr als eine Klasse zu erweitern, aber das Implementieren vieler Interfaces erlaubt?
Was this page helpful?