Java Interface Default- und Static-Methoden
Default- und Static-Methoden in Java-Interfaces einfügen, um APIs weiterzuentwickeln, ohne bestehende Implementierungen zu brechen.
Bis Java 7 konnte ein Interface nur abstrakte Methoden deklarieren — der Methodenrumpf lebte in den implementierenden Klassen. Java 8 hat drei neue Dinge hinzugefügt, die man in einem Interface verwenden kann:
default-Methoden — eine Methode mit einem Rumpf, die von Implementoren geerbt wird, die sie nicht überschreiben.static-Methoden — Hilfsmethoden, die dem Interface selbst gehören.private-Methoden (Java 9) — Helfer, die von dendefault- undstatic-Methoden des Interfaces gemeinsam genutzt werden.
Der Hauptanwendungsfall war die API-Evolution. Nachdem java.util.Collection seit fünfzehn Jahren mit Millionen von Implementierungen existierte, wollte das Java-Team stream() hinzufügen, ohne alle zu brechen. default Stream<E> stream() { ... } hat genau das getan.
Default-Methoden
Eine default-Methode hat einen Rumpf. Implementierende Klassen erben sie kostenlos und können sie überschreiben, wenn sie ein anderes Verhalten wünschen:
public interface Greeter {
String name();
default String greet() {
return "Hello, " + name() + "!";
}
}
public class English implements Greeter {
public String name() { return "Alice"; }
}
public class Loud implements Greeter {
public String name() { return "Bob"; }
public String greet() { return "HEY " + name().toUpperCase() + "!"; } // override
}
new English().greet(); // Hello, Alice!
new Loud().greet(); // HEY BOB!default ist nur ein Marker — das Schlüsselwort sagt dem Compiler: „Das ist ein Methodenrumpf innerhalb eines Interfaces." Ohne es hat eine Interface-Methode keinen Rumpf.
Wie jedes nicht-private Interface-Mitglied ist eine default-Methode implizit public; man kann sie nicht protected oder package-private machen. In den obigen Beispielen gibt es kein public-Schlüsselwort, weil es die einzige Option ist, die der Compiler zulässt.
Das Hinzufügen einer default-Methode zu einem bestehenden Interface ist eine nicht brechende Änderung. Implementoren, die sie nicht überschreiben, erben den Standard. Das ist die Eigenschaft, die Interface-Evolution ermöglicht.
Static-Methoden auf Interfaces
Eine static-Methode auf einem Interface gehört dem Interface, nicht Instanzen. Man ruft sie über den Interface-Namen auf:
public interface Path {
static Path of(String s) { return new SimplePath(s); }
String value();
}
Path p = Path.of("/tmp/foo");Ein häufiger Einsatz sind Factory-Methoden, die Instanzen des Interfaces erzeugen — Path.of, List.of, Map.of, Stream.of. Sie erlauben Aufrufern, vom Interface abhängig zu sein, auch beim Erstellen von Objekten, anstatt eine bestimmte Implementierungsklasse wählen zu müssen.
Static-Interface-Methoden werden nicht vererbt. Man ruft sie immer über den Interface-Namen auf, nie über eine Unterklasse.
Private-Methoden (Java 9+)
Eine private-Methode auf einem Interface ist ein Helfer, der nur für andere Methoden desselben Interfaces sichtbar ist. Er erlaubt zwei default-Methoden, Logik zu teilen, ohne diese Logik den Implementoren preiszugeben:
public interface Logger {
default void info(String msg) { log("INFO", msg); }
default void warn(String msg) { log("WARN", msg); }
private void log(String level, String msg) {
System.out.println("[" + level + "] " + msg);
}
}Ohne private müsste man entweder den Rumpf in beide default-Methoden kopieren oder log als default-Methode selbst exponieren — was Implementoren erlauben würde, sie zu überschreiben, was man wahrscheinlich nicht möchte.
Was Default-Methoden nicht können
default-Methoden leben auf dem Interface, das keinen Zustand hat. Sie können abstrakte Methoden und andere default/static-Methoden aufrufen, aber sie können keine Instanzfelder lesen oder schreiben — es gibt keine zu lesen.
public interface Counter {
int get();
void increment();
default void incrementTwice() { // ok — only calls abstract methods
increment();
increment();
}
}Das begrenzt, wie viel echtes Verhalten eine default-Methode tragen kann. Sie sind am besten für dünne Komfortschichten über den abstrakten Methoden geeignet, nicht um die Implementierung vollständig zu ersetzen.
Diamond-Konflikte
Wenn eine Klasse zwei Interfaces implementiert, die beide eine default-Methode mit derselben Signatur bereitstellen, muss die Klasse den Konflikt explizit auflösen:
interface A { default String hello() { return "A"; } }
interface B { default String hello() { return "B"; } }
class C implements A, B {
@Override
public String hello() {
return A.super.hello() + B.super.hello(); // explicit pick
}
}A.super.hello() ruft A's Default auf; B.super.hello() ruft B's auf. Der Compiler weigert sich, class C implements A, B { } ohne eine Überschreibung zu kompilieren — es gibt keinen automatischen Gewinner. Das ist Javas Antwort auf das Multiple-Inheritance-„Diamond-Problem" — wenn es auftritt, muss die Klasse die Entscheidung treffen.
Eine Unterklasse gewinnt auch über einen geerbten Default: Wenn eine konkrete Superklasse hello() bereitstellt, gewinnt diese über jede default-Methode eines Interfaces.
Wann eine Default-Methode hinzuzufügen ist
default ist das richtige Werkzeug, wenn:
- Man ein bestehendes, weit verbreitetes Interface erweitern möchte, ohne Implementoren zu brechen.
- Die neue Methode wirklich aus den bestehenden abstrakten Methoden ableitbar ist.
- Der Default „offensichtlich korrekt" ist — die meisten Implementoren werden damit zufrieden sein.
Es ist das falsche Werkzeug, wenn:
- Man versucht ist, echtes zustandsbasiertes Verhalten dort einzubringen (stattdessen eine abstrakte Klasse verwenden).
- Der Default tatsächlich nicht nützlich ist und Implementoren ihn trotzdem alle überschreiben werden (einfach abstrakt lassen).
Ein ausgearbeitetes Beispiel
Was kommt als Nächstes
Interfaces und abstrakte Klassen betreffen, wie Typen zwischen Dateien in Beziehung stehen. Das nächste Unterthema — verschachtelte Klassen — handelt davon, wie Typen innerhalb einer einzelnen Datei in Beziehung stehen: Klassen, die innerhalb anderer Klassen deklariert werden, und die vier Varianten, die Java dafür anbietet. Weiter zu verschachtelten Klassen.