Menu

Java-Vererbung: extends, super und Überschreiben

Wie eine Java-Unterklasse mit extends Felder und Methoden erbt, über super die Oberklasse aufruft und Verhalten überschreibt - samt der häufigsten Stolperfallen.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Vererbung ist Wiederverwendung nach dem "ist-ein"-Prinzip

Vererbung erlaubt es einer neuen Klasse, auf einer bestehenden aufzubauen. Die neue Klasse -die Unterklasse- erhält automatisch die Felder und Methoden der Oberklasse und fügt dann hinzu oder ändert, was sie braucht. Du greifst dazu, wenn ein Typ eine speziellere Ausprägung eines anderen ist: ein Dog ist ein Animal, ein SavingsAccount ist ein BankAccount.

Du schreibst es mit dem Schlüsselwort extends. Alles, was im Elternteil public oder protected ist, steht im Kind zur Verfügung, ohne dass du es neu schreiben musst.

Dog deklariert weder name noch eat(), und doch besitzt es beides. Genau das ist der Sinn: gemeinsames Verhalten lebt an einer einzigen Stelle.

super: den Elternteil erreichen

Der Konstruktor einer Unterklasse muss dafür sorgen, dass der Elternteil zuerst aufgebaut wird. Das tust du mit super(...), was den Konstruktor des Elternteils aufruft und die erste Anweisung im Konstruktor der Unterklasse sein muss. Lässt du es weg, fügt Java stillschweigend einen Aufruf des argumentlosen Konstruktors des Elternteils ein - und wenn der Elternteil keinen solchen Konstruktor hat, lässt sich der Code nicht kompilieren.

Die Ausgabe zeigt, dass der Konstruktor des Elternteils vor dem Rumpf des Kindes läuft - die Konstruktion fließt von der Spitze der Hierarchie nach unten.

Methoden überschreiben

Eine Unterklasse kann eine geerbte Methode ersetzen, indem sie sie mit derselben Signatur neu definiert. Das ist das Überschreiben, und du solltest es immer mit @Override kennzeichnen. Die Annotation ist nicht erforderlich, aber sie lässt den Compiler prüfen, ob du tatsächlich eine Methode des Elternteils getroffen hast - sie fängt Tippfehler wie tostring() statt toString() ab, die sonst stillschweigend eine völlig neue Methode erzeugen würden.

Obwohl das Array vom Typ Animal ist, führt jedes Element seine eigene speak()-Methode aus. Java wählt die Methode anhand des tatsächlichen Objekts zur Laufzeit aus, nicht anhand des deklarierten Typs der Variablen - das ist die Grundlage des Polymorphismus.

Die Version des Elternteils mit super.method() aufrufen

Überschreiben muss die Arbeit des Elternteils nicht wegwerfen. Verwende super.method(), um die geerbte Version auszuführen und sie dann zu ergänzen:

Ohne super. würde der Aufruf von log innerhalb von TimestampLogger.log sich selbst aufrufen und endlos rekursieren. super. bedeutet ausdrücklich "die Version des Elternteils".

Geerbte Felder und Zugriff

Eine Unterklasse sieht die public- und protected-Member des Elternteils, aber nicht dessen private-Member. private-Felder existieren weiterhin im Objekt - die eigenen Methoden des Elternteils können auf sie zugreifen - aber die Unterklasse kann sie nicht direkt referenzieren. Verwende protected, wenn du möchtest, dass Unterklassen Zugriff haben, während ein Member vor nicht verwandtem Code verborgen bleibt.

class Base {
    private int secret;      // für Unterklassen unsichtbar
    protected int shared;    // für Unterklassen sichtbar
}

class Derived extends Base {
    void demo() {
        shared = 5;          // OK
        // secret = 5;       // Kompilierfehler - private in Base
    }
}

Auch deshalb muss der Konstruktor einer Unterklasse oft super(...) aufrufen: Es ist die einzige Möglichkeit, den privaten Zustand des Elternteils zu initialisieren.

Vererbung mit final stoppen

Manchmal soll eine Klasse überhaupt nicht erweitert werden - String ist genau aus diesem Grund final. Eine Klasse als final zu markieren, verbietet das Ableiten von Unterklassen; eine Methode als final zu markieren, verbietet ihr Überschreiben, erlaubt aber weiterhin das Erweitern der Klasse.

final class Constants { }            // kann nicht abgeleitet werden

class Config {
    final void load() { }            // Unterklassen dürfen Config erweitern,
                                     // aber load() nicht überschreiben
}

Greife zu final, wenn das Verhalten einer Klasse im gesamten Programm garantiert und unveränderlich sein muss - es ist ein bewusstes "nicht erweitern"-Signal, nicht die Standardeinstellung.

Eine häufige Stolperfalle: Komposition bevorzugen, wenn es kein "ist-ein" ist

Vererbung ist verlockend, weil sie Code wiederverwendet, aber sie koppelt das Kind eng an den Elternteil. Wenn die Beziehung kein echtes "ist-ein" ist - etwa ein Car, das zufällig einen Engine braucht - mach nicht Car extends Engine. Ein Auto hat einen Motor, es ist keiner. Modelliere das stattdessen mit einem Feld (Komposition):

class Car {
    private Engine engine = new Engine();   // Car HAT-EIN Engine

    void start() { engine.ignite(); }
}

Verwende Vererbung nur, wenn die Unterklasse wirklich eine spezialisierte Form der Oberklasse ist und du ihr Verhalten erben und ersetzen willst.

Als Nächstes: Schnittstellen

Vererbung mit extends gibt dir einen einzigen Elternteil und eine gemeinsame Implementierung. Aber eine Klasse kann nur eine einzige Klasse erweitern - wie gibst du also nicht verwandten Klassen eine gemeinsame Fähigkeit? Genau dafür gibt es Schnittstellen: ein Vertrag, den viele Klassen implementieren können, und das Thema der nächsten Seite.

Häufig gestellte Fragen

Was ist Vererbung in Java?

Vererbung erlaubt es einer Klasse (der Unterklasse), die Felder und Methoden einer anderen Klasse (der Oberklasse) mithilfe des Schlüsselworts extends wiederzuverwenden. Die Unterklasse erhält automatisch die public- und protected-Member des Elternteils und kann neue hinzufügen oder geerbtes Verhalten durch Überschreiben ersetzen. Sie modelliert eine "ist-ein"-Beziehung: ein Dog ist ein Animal.

Was bewirkt das Schlüsselwort super in Java?

super verweist auf die Oberklasse. super(...) in einem Konstruktor ruft den Konstruktor des Elternteils auf (und muss die erste Anweisung sein), während super.method() die Version einer Methode aufruft, die du überschrieben hast, aus der Oberklasse. So kann eine Unterklasse auf der Logik des Elternteils aufbauen, anstatt sie vollständig zu ersetzen.

Was ist der Unterschied zwischen Überschreiben und Überladen in Java?

Beim Überschreiben (Overriding) wird eine geerbte Methode in einer Unterklasse mit derselben Signatur neu definiert, wodurch sich das Verhalten ändert - markiere es mit @Override. Beim Überladen (Overloading) werden mehrere Methoden mit demselben Namen, aber unterschiedlichen Parameterlisten in derselben Klasse definiert. Beim Überschreiben geht es um Vererbung und Dispatch zur Laufzeit; beim Überladen handelt es sich lediglich um zwei Methoden, die zufällig denselben Namen tragen.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S