HOWTO · Java

Erweitern von zwei Klassen in Java

Erfahren Sie, warum eine Java-Klasse nur eine Klasse erweitern kann und wie Interfaces und Komposition Verhalten aus mehreren Typen kombinieren.

Auf dieser Seite

Eine Java-Klasse kann nicht zwei Klassen erweitern. Jede Klasse hat genau eine direkte Oberklasse. Eine Deklaration wie class Child extends First, Second ist daher kein gültiges Java. Um Verhalten mehrerer Typen zu kombinieren, können Sie eine Klasse erweitern und beliebig viele Interfaces implementieren oder andere Objekte als Felder halten und ihnen Aufgaben im Rahmen einer Komposition übertragen.

Warum eine Java-Klasse nicht zwei Klassen erweitern kann

Java verwendet bei Klassen die Einfachvererbung: Jede Klasse kann mit extends höchstens eine Oberklasse angeben. Dadurch hat ein Objekt nur eine Klassenvererbungskette. Außerdem wird vermieden, dass unklar ist, welche Implementierung oder welcher Zustand einer Oberklasse gelten soll, wenn zwei Elternklassen dasselbe Element definieren. Der Compiler weist eine Klassendeklaration mit zwei Oberklassen zurück. Das ist eine Einschränkung zur Compile-Zeit und kein Fehler, der erst während der Programmausführung auftritt.

Die folgende Deklaration ist absichtlich ungültige Java-Syntax und lässt sich nicht kompilieren: class Child extends First, Second {}. Verwenden Sie eine Oberklasse, wenn die neue Klasse tatsächlich eine spezialisierte Form dieses Typs ist. Der nächste Abschnitt zeigt, wie Interface-Verträge zusätzliche Fähigkeiten beschreiben können.

Eine Oberklasse erweitern und mehrere Interfaces implementieren

Eine Klasse kann eine Klasse erweitern und mehrere Interfaces implementieren. Die Oberklasse kann gemeinsamen Zustand oder eine Implementierung bereitstellen, während jedes Interface eine Fähigkeit festlegt, zu der sich die Klasse verpflichtet. Die Java-Schlüsselwörter sind dabei entscheidend: Für die Oberklasse verwendet eine Klasse extends, für Interfaces implements. Ein Interface kann seinerseits mehrere Interfaces erweitern. Dadurch erhält eine Klasse jedoch keine mehreren Klassenoberklassen.

Dieses vollständige Beispiel definiert zwei Interface-Verträge und implementiert beide zusammen mit einer Oberklasse:

public class CatalogItem extends Record implements Printable, Identifiable {
    CatalogItem(String name) {
        super(name);
    }

    @Override
    public void printLabel() {
        System.out.println(name());
    }

    @Override
    public String id() {
        return "item-" + name().toLowerCase();
    }

    public static void main(String[] args) {
        CatalogItem item = new CatalogItem("Book");
        item.printLabel();
        System.out.println(item.id());
    }

}

interface Printable {
    void printLabel();
}

interface Identifiable {
    String id();
}

class Record {
    private final String name;

    Record(String name) {
        this.name = name;
    }

    String name() {
        return name;
    }
}
Book
item-book

CatalogItem übernimmt die Implementierung von name() von Record und stellt die von beiden Interfaces verlangten Methoden bereit. Interfaces sind nützlich, wenn nicht verwandte Klassen einen Typ- oder Fähigkeitsvertrag gemeinsam haben sollen. Anders als eine zweite Oberklasse stellen sie nicht automatisch Zustand oder eine Klassenimplementierung bereit.

Verhalten von Klassen mit Komposition kombinieren

Bei einer Komposition werden Objekte über eine Hat-ein-Beziehung kombiniert: Eine Klasse speichert Referenzen auf andere Objekte und ruft deren Methoden auf. Das ist nützlich, wenn die Verhaltensweisen aus unabhängig verwendbaren Klassen stammen oder wenn sich ein verwendetes Objekt austauschen lassen soll. Die enthaltende Klasse bestimmt, welche Operationen sie anbietet, und kann gezielt nur die Arbeit delegieren, die ihre Aufrufer benötigen.

Hier enthält ein SmartDevice einen PowerController und einen MessageSender und delegiert Operationen an beide:

public class SmartDevice {
    private final PowerController power = new PowerController();
    private final MessageSender sender = new MessageSender();

    void start() {
        power.powerOn();
    }

    void sendMessage(String message) {
        sender.send(message);
    }

    public static void main(String[] args) {
        SmartDevice device = new SmartDevice();
        device.start();
        device.sendMessage("Ready");
    }

}

class PowerController {
    void powerOn() {
        System.out.println("Power on");
    }
}

class MessageSender {
    void send(String message) {
        System.out.println("Sending: " + message);
    }
}
Power on
Sending: Ready

SmartDevice wird weder zu einem PowerController noch zu einem MessageSender, sondern verwendet beide. In einem größeren Programm können die verwendeten Objekte über einen Konstruktor übergeben oder durch Interfaces repräsentiert werden. So lassen sich Implementierungen leichter austauschen und die enthaltende Klasse unabhängig testen.

Das passende Wiederverwendungsmuster auswählen

Verwenden Sie Klassenvererbung, wenn der neue Typ tatsächlich eine spezialisierte Form einer einzelnen Oberklasse ist und deren Implementierung erben soll. Ergänzen Sie Interfaces, wenn ein Typ Fähigkeiten zusichern soll, die auch andere Klassen anbieten. Verwenden Sie Komposition, wenn Verhalten aus einzelnen Bestandteilen zusammengesetzt, delegiert oder unabhängig geändert werden soll.

Geben Sie jeder Klasse eine klar umrissene Aufgabe und stellen Sie nur die Operationen bereit, die ihre Aufrufer benötigen. Bevorzugen Sie kleine Interface-Verträge statt umfangreicher Interfaces und vermeiden Sie, Verhalten zu duplizieren, das in ein gemeinsam verwendetes Objekt gehört. So lassen sich Beziehungen leichter verstehen, testen und an geänderte Anforderungen anpassen. Kurz gesagt: Java erlaubt einer Klasse nicht, zwei Klassen zu erweitern. Eine Oberklasse, mehrere Interface-Verträge und zusammengesetzte Objekte decken die üblichen Anforderungen an die Wiederverwendung ab.