CodeGym /Kurse /JAVA 25 SELF /Default-Methoden in Schnittstellen

Default-Methoden in Schnittstellen

JAVA 25 SELF
Level 21 , Lektion 2
Verfügbar

1. Einleitung

Früher (vor Java 8) waren Interfaces sehr strikt: Man konnte darin nur abstrakte Methoden (ohne Implementierung) und Konstanten (public static final) deklarieren. Das war bequem, bis ein großes Problem auftauchte: die Weiterentwicklung von Bibliotheken.

Stellen Sie sich folgende Situation vor

Sie haben eine beliebte Bibliothek entwickelt, in der es ein Interface gibt:

public interface Movable {
    void move(int x, int y);
}

Tausende Programmierer weltweit schreiben Klassen, die dieses Interface implementieren. Nach ein paar Jahren stellen Sie fest, dass allen eine Methode reset() fehlt, die das Objekt in die Ausgangsposition zurücksetzt. Sie fügen dem Interface hinzu:

public interface Movable {
    void move(int x, int y);
    void reset();
}

Und dann beginnt der Albtraum: Alle Projekte, die Ihr Interface verwenden, lassen sich nicht mehr kompilieren! Denn nun müssen sie die neue Methode implementieren, von der niemand wusste. Die Migration wird zur Qual.

Default-Methoden – die Lösung!

Java 8 hat Default-Methoden eingeführt: Man kann jetzt eine Methode mit Implementierung direkt im Interface hinzufügen! Alle bestehenden Klassen erhalten automatisch eine Standardimplementierung, und ihr Code bricht nicht. Und wenn man möchte – kann man die Methode auf eigene Weise überschreiben.

2. Syntax der Default-Methoden

Eine Default-Methode ist eine normale Methode mit Implementierung innerhalb eines Interfaces, gekennzeichnet durch das Schlüsselwort default.

public interface Movable {
    void move(int x, int y);

    default void reset() {
        // Typische Implementierung: Zurück zum Ursprung der Koordinaten
        move(0, 0);
    }
}

Erläuterung:

  • Alle Methoden eines Interfaces sind standardmäßig public und abstract, doch Default-Methoden sind nicht abstrakt, sondern haben einen Methodenrumpf.
  • Das Schlüsselwort default steht immer vor dem Rückgabetyp der Methode.

Wie sieht das in einer Klasse aus?

public class Robot implements Movable {
    private int x, y;

    @Override
    public void move(int x, int y) {
        this.x = x;
        this.y = y;
        System.out.println("Roboter wurde verschoben nach (" + x + ", " + y + ")");
    }

    // reset() muss nicht implementiert werden – die Default-Version funktioniert!
}

Rufen wir nun reset() bei einem Objekt Robot auf, greift die Implementierung aus dem Interface Movable:

public class Main {
    public static void main(String[] args) {
        Movable robot = new Robot();
        robot.move(10, 20); // Roboter wurde verschoben nach (10, 20)
        robot.reset();      // Roboter wurde verschoben nach (0, 0)
    }
}

3. Default-Methoden in der Standardbibliothek

Default-Methoden wurden eingeführt, um die umfangreichen Standard-Interfaces von Java weiterentwickeln zu können, ohne alten Code zu brechen.

Beispiel: Interface List (Java 8+)

In Java 8 wurden dem Interface List Methoden mit Implementierung hinzugefügt, z. B. forEach, replaceAll, sort:

default void forEach(Consumer<Entity> action) {
    for (Entity e : this) {
        action.accept(e);
    }
}

Wenn Sie Ihre eigene Liste implementieren und forEach nicht überschreiben, funktioniert es dennoch – dank der Default-Methode.

Mehr über Generics (Consumer<Entity>) erfahren Sie im Level 26 :P

4. Wozu braucht man Default-Methoden?

  • Weiterentwicklung von APIs ohne Codebruch: Man kann neue Methoden in ein Interface aufnehmen, ohne dass sie in allen vorhandenen Klassen implementiert werden müssen.
  • Universelle Verhaltensschablonen: Man kann ein Standardverhalten deklarieren, das Klassen nutzen oder überschreiben können.
  • Weniger Duplikate: Wenn sich ein Verhalten für die meisten Implementierungen gleicht, muss der Code nicht in jede Klasse kopiert werden.

Analogie

Stellen Sie sich vor, Sie haben einen Mietvertrag (Interface). Früher stand darin: „Der Mieter ist verpflichtet, Wasser zu bezahlen.“ Später kam hinzu: „Der Mieter ist verpflichtet, Strom zu bezahlen.“ Ohne Default-Methoden müssten Sie alle Verträge mit allen Mietern neu schreiben! Mit Default-Methoden fügen Sie einfach eine Klausel hinzu, und wenn jemand etwas anderes braucht – kann er sich individuell einigen.

5. Einschränkungen und Besonderheiten von Default-Methoden

Default-Methoden können Methoden der Klasse Object nicht überschreiben

Sie können in einem Interface keine Default-Methode mit einer Signatur deklarieren, die mit equals, hashCode oder toString aus der Klasse Object übereinstimmt. Das verhindert Verwirrung: Jedes Objekt in Java hat diese Methoden bereits.

// Compilerfehler!
interface Broken {
    default boolean equals(Object obj) { return false; }
}

Konflikte von Default-Methoden

Was, wenn eine Klasse zwei Interfaces implementiert, die jeweils eine Default-Methode mit identischer Signatur haben? Der Java-Compiler sagt ehrlich: „Entscheide selbst, ich weiß nicht, was ich tun soll!“

interface A {
    default void hello() { System.out.println("Hello from A"); }
}

interface B {
    default void hello() { System.out.println("Hello from B"); }
}

class C implements A, B {
    // Konflikt muss zwingend aufgelöst werden:
    @Override
    public void hello() {
        // Man kann wählen, wessen Methode aufgerufen wird, oder eine eigene Implementierung schreiben
        A.super.hello(); // oder B.super.hello();
    }
}

Wenn man hello() in der Klasse C nicht implementiert, führt das zu einem Compilerfehler.

Default-Methoden können andere Interface-Methoden aufrufen

Eine Default-Methode kann andere Methoden des Interfaces aufrufen, sogar abstrakte. Wichtig ist nur, dass es im konkreten Klassen-Typ eine Implementierung gibt.

interface Printer {
    void print(String text);

    default void printTwice(String text) {
        print(text);
        print(text);
    }
}

6. Beispiel: Wir entwickeln eine Anwendung mit einer Default-Methode

Schauen wir uns ein Beispiel für die Verwendung von Default-Methoden im Interface Movable an:

public interface Movable {
    void move(int x, int y);

    default void reset() {
        move(0, 0);
    }
}

Und es gibt eine Klasse Robot, die dieses Interface implementiert:

public class Robot implements Movable {
    private int x = 5;
    private int y = 7;

    @Override
    public void move(int x, int y) {
        this.x = x;
        this.y = y;
        System.out.println("Roboter wurde verschoben nach (" + x + ", " + y + ")");
    }

    // reset() wird nicht implementiert – wir verwenden die Default-Methode!
}

Versuchen wir nun, beide Methoden aufzurufen:

public class Main {
    public static void main(String[] args) {
        Movable robot = new Robot();
        robot.move(10, 20); // Roboter wurde verschoben nach (10, 20)
        robot.reset();      // Roboter wurde verschoben nach (0, 0)
    }
}

Wenn wir möchten, dass Robot sich auf besondere Weise zurücksetzt – überschreiben wir einfach reset() in der Klasse:

@Override
public void reset() {
    System.out.println("Der Roboter fährt herunter und kehrt zur Basis zurück!");
    move(0, 0);
}

7. Default-Methoden und die Mehrfachimplementierung von Interfaces

Default-Methoden sind besonders nützlich, wenn eine Klasse mehrere Interfaces implementiert. Aber es gibt eine Nuance: Haben beide Interfaces eine Default-Methode mit identischer Signatur, verlangt der Compiler eine explizite Konfliktauflösung.

Konfliktbeispiel

interface A {
    default void show() { System.out.println("A"); }
}
interface B {
    default void show() { System.out.println("B"); }
}
class C implements A, B {
    @Override
    public void show() {
        // Wir wählen explizit, wessen Default-Methode wir verwenden
        A.super.show(); // oder B.super.show();
    }
}

8. Schema: wie der Aufruf einer Default-Methode funktioniert


+-------------------+
|   Movable         |
|-------------------|
| +move(int, int)   | <- abstrakte Methode
| +reset()          | <- Default-Methode
+-------------------+
         ^
         |
+-------------------+
|   Robot           |
|-------------------|
| +move(int, int)   | <- implementiert
|                   | (reset() nicht implementiert)
+-------------------+
         |
     Aufruf von reset()
         |
   Es wird die Implementierung
   aus dem Interface Movable verwendet
Aufruf einer Default-Methode: Standardimplementierung aus dem Interface

9. Typische Fehler im Umgang mit Default-Methoden

Fehler Nr. 1: Versuch, eine Default-Methode ohne Implementierung zu definieren.
Eine Default-Methode muss einen Rumpf haben! Wenn Sie default void foo(); schreiben, sagt der Compiler sofort: „Hast du die geschweiften Klammern vergessen?“

Fehler Nr. 2: Konflikt von Default-Methoden aus verschiedenen Interfaces.
Wenn eine Klasse zwei Interfaces mit derselben Default-Methode implementiert, müssen Sie den Konflikt explizit lösen – sonst lässt der Compiler den Code nicht kompilieren.

Fehler Nr. 3: Versuch, eine Default-Methode mit der Signatur einer Methode aus Object zu deklarieren.
Man kann in einem Interface keine Default-Methode equals, hashCode oder toString erstellen – nur abstrakte Methoden mit diesen Namen.

Fehler Nr. 4: Vergessen, dass Default-Methoden keine „Magie“ sind, sondern nur ein praktisches Mittel.
Default-Methoden heben den Grundsatz nicht auf, dass ein Interface ein Vertrag ist. Wenn das Standardverhalten nicht passt – überschreiben Sie die Default-Methode in der Klasse.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION