Vererbung in Java: Klassen sinnvoll erweitern
Bei der Vererbung entsteht aus einer allgemeinen Klasse eine speziellere Klasse. Die Unterklasse übernimmt verwendbare Merkmale der Oberklasse, kann neue ergänzen und Methoden passend überschreiben. Sinnvoll ist das nur, wenn wirklich eine Ist-eine-Art-von-Beziehung besteht und Objekte der Unterklasse die Erwartungen an die Oberklasse erfüllen.
Hake ab, was du schon kannst — und komm am Ende hierher zurück!
Was wird bei der Vererbung übernommen?
Zur Erinnerung: Eine Klasse beschreibt den Aufbau und das Verhalten ihrer Objekte. Ein Attribut speichert Daten, eine Methode führt eine Aktion aus.
Stell dir ein fiktives Inventarsystem vor. Jedes Produkt besitzt eine Artikelnummer, einen Preis und einen Hersteller. Ein Buch ist eine besondere Art von Produkt und ergänzt Autor und ISBN.
Vererbung
Vererbung ist eine dauerhafte Beziehung zwischen Klassen. Eine Unterklasse wird aus einer allgemeineren Oberklasse abgeleitet. Sie übernimmt deren zugängliches Verhalten und kann die Klasse spezialisieren.
Für das Beispiel gilt:
Produktist die Oberklasse.Buchist die Unterklasse.- Ein Buch ist ein Produkt.
- Ein Buch übernimmt allgemeine Produktmerkmale und ergänzt buchtypische Merkmale.
Eine Unterklasse kann auf drei Arten spezialisiert werden:
- übernehmen: Eine geerbte Methode unverändert verwenden,
- ergänzen: neue Attribute oder Methoden hinzufügen,
- überschreiben: eine geerbte Methode mit passendem Spezialverhalten neu implementieren.
Vererbung wirkt über mehrere Stufen. Wenn Dungeon von Level und Level von GameObject erbt, ist ein Dungeon auch ein GameObject. Eine solche Kette heißt mehrstufige Vererbung.
Frage zuerst: „Ist jedes Objekt der Unterklasse auch sinnvoll als Objekt der Oberklasse verwendbar?“ Ein gemeinsames Merkmal allein reicht für Vererbung nicht aus.
Wie wird eine Unterklasse in Java geschrieben?
Java verwendet das Schlüsselwort extends. Die Grundform lautet: class Unterklasse extends Oberklasse { ... }.
Die Oberklasse Produkt stellt die Methode beschreibung() bereit. Die Unterklasse wird mit class Buch extends Produkt { ... } deklariert. Ein Buch kann die geerbte Methode verwenden und zusätzlich etwa das Attribut isbn sowie eine Methode autorAnzeigen() besitzen.
Gemeinsame Produktlogik bleibt dadurch in Produkt. Nur die buchtypischen Ergänzungen stehen in Buch.
Eine Java-Klasse hat höchstens eine direkte Oberklasse. Mehrere gemeinsame Verträge können über Interfaces beschrieben werden: Ein Interface legt beispielsweise fest, welche Methoden eine Klasse anbieten muss. Eine Klasse kann mehrere Interfaces mit implements umsetzen. Das ist von mehreren direkten Oberklassen zu unterscheiden.
Die Sichtbarkeit bestimmt, worauf anderer Code zugreifen darf. Für unsere Beispiele verwenden wir eigenständige, nicht ineinander verschachtelte Klassen. Ein Paket fasst Klassen unter einem gemeinsamen Namen zusammen.
public: zugänglich, sofern auch die Klasse zugänglich ist,protected: im selben Paket und unter besonderen Regeln auch aus Unterklassen anderer Pakete zugänglich,- ohne Zugriffsmodifikator: nur innerhalb desselben Pakets zugänglich,
private: in unseren eigenständigen Klassen nur innerhalb der deklarierenden Klasse direkt zugänglich.
Außerhalb des Pakets darf eine Unterklasse auf geschützte Instanzmitglieder über this oder über eine Referenz ihres eigenen Typs beziehungsweise eines weiteren Untertyps zugreifen. Eine beliebige Referenz vom Oberklassentyp genügt dafür nicht.
Private Mitglieder werden in Java nicht vererbt. Trotzdem enthält ein Unterklassenobjekt auch die privaten Instanzattribute seiner Oberklasse. Die eigenständige Unterklasse greift darauf bei Bedarf über geeignete Methoden der Oberklasse zu. Bei verschachtelten Klassen gelten zusätzliche Zugriffsrechte innerhalb der gemeinsamen äußersten Klasse.
Wähle in jeder Lücke die passende Form und prüfe anschließend deine Antworten.
In Java verbindet das Schlüsselwort eine Unterklasse mit ihrer Oberklasse. Ein vor direktem Zugriff aus einer eigenständigen Unterklasse geschütztes Attribut ist . Eine Unterklasse kann eine geerbte Methode , wenn die Methode dies erlaubt.
extends bezeichnet Klassenvererbung. private verhindert in unseren eigenständigen Klassen den direkten Zugriff von außen und aus Unterklassen. Beim Überschreiben erhält eine geerbte Methode eine passende neue Implementierung.extends bezeichnet Klassenvererbung. private verhindert in unseren eigenständigen Klassen den direkten Zugriff von außen und aus Unterklassen. Beim Überschreiben erhält eine geerbte Methode eine passende neue Implementierung.Wie funktionieren Überschreiben und Polymorphie?
Beim Überschreiben implementiert die Unterklasse eine Instanzmethode der Oberklasse neu. Eine Instanzmethode wird für ein bestimmtes Objekt aufgerufen. In unseren einfachen Beispielen müssen Methodenname sowie Anzahl, Reihenfolge und Typen der Parameter übereinstimmen; dies beschreibt die Signatur. Auch der Rückgabetyp muss kompatibel sein, und die Sichtbarkeit darf nicht eingeschränkt werden.
@Override lässt den Java-Compiler prüfen, ob die Methode tatsächlich eine Methode eines Obertyps überschreibt oder umsetzt. Die Annotation erzeugt die Überschreibung nicht erst. Eine final-Methode darf nicht überschrieben werden. static-Methoden gehören zur Klasse und werden bei gleichem Namen und gleicher Signatur verdeckt, nicht dynamisch überschrieben.
Polymorphie
Polymorphie bedeutet hier: Eine Variable des Oberklassentyps kann auf Objekte verschiedener Unterklassen verweisen. Beim Aufruf einer überschriebenen Instanzmethode wird die zum konkreten Objekt passende Implementierung ausgeführt.
In einem fiktiven Zeichenprogramm legt die abstrakte Klasse Shape die Methode draw() fest. Abstrakt bedeutet: Du kannst kein Objekt direkt mit new Shape() erzeugen. Die konkreten Unterklassen Rectangle und Circle implementieren draw() jeweils. Beide besitzen hier einen zugänglichen parameterlosen Konstruktor.
So liest du die Aufrufe Schritt für Schritt:
Shape s1 = new Rectangle();Shape s2 = new Circle();s1.draw();führt die Implementierung ausRectangleaus.s2.draw();führt die Implementierung ausCircleaus.
Der deklarierte Variablentyp ist in beiden Fällen Shape. Das konkrete Objekt entscheidet zur Laufzeit über die ausgeführte überschreibende Methode. Dies nennt man dynamische Methodenbindung.
Über eine Variable des Oberklassentyps sind nur die dort bekannten und an der Aufrufstelle zugänglichen Mitglieder direkt erreichbar. Unterklassenspezifische Methoden werden dadurch nicht automatisch sichtbar. Ein Upcast von der Unterklasse zur Oberklasse ist dennoch möglich, weil das Unterklassenobjekt als Oberklassenobjekt verwendbar sein soll.
Wie wird der Oberklassenteil konstruiert?
Ein Konstruktor initialisiert ein neu erzeugtes Objekt. Er trägt den Klassennamen und hat keinen Rückgabetyp. Konstruktoren werden in Java nicht vererbt. Beim Erzeugen eines Unterklassenobjekts muss jedoch auch sein Oberklassenteil initialisiert werden. Mit super(...) ruft der Unterklassenkonstruktor dafür einen passenden Oberklassenkonstruktor auf. Es entsteht dabei ein einziges Unterklassenobjekt, kein zusätzliches eigenständiges Oberklassenobjekt.
In einem fiktiven Spiel besitzt GameObject den Konstruktor GameObject(String typ). Level erbt von GameObject und ergänzt die Zahl stufe.
Der Konstruktor von Level folgt diesem Ablauf:
Level(String typ, int stufe)wird aufgerufen.super(typ)initialisiert zuerst denGameObject-Teil.- Danach setzt der Unterklassenkonstruktor sein eigenes Attribut
stufe.
Für dieses Beispiel schreiben wir den Konstruktor so: Level(String typ, int stufe) { super(typ); this.stufe = stufe; }. Dabei bezeichnet this.stufe das Attribut des neuen Objekts, stufe rechts vom Gleichheitszeichen den übergebenen Parameter.
Hier steht super(typ) zuerst. In Java 25 dürfen davor auch bestimmte Anweisungen stehen, etwa Prüfungen von Parametern. Die pauschale Regel „super(...) muss immer die erste Anweisung sein“ gilt daher nicht versionsübergreifend. Die hier gezeigte Schreibweise funktioniert auch in älteren Java-Versionen.
Fehlt sowohl ein ausdrückliches super(...) als auch this(...), beginnt der Konstruktor implizit mit super(). Dann muss ein zugänglicher parameterloser Oberklassenkonstruktor existieren. Mit this(...) kann ein Konstruktor zunächst an einen anderen Konstruktor derselben Klasse weiterleiten; diese Kette muss schließlich einen passenden Oberklassenkonstruktor erreichen.
Eine überschreibende Methode kann mit super.methodenname(...) gezielt eine zugängliche konkrete Implementierung aus der Oberklassenhierarchie aufrufen. Die Suche beginnt bei der direkten Oberklasse; die dort gefundene Methode kann auch von einer weiteren Oberklasse stammen. So kann sie allgemeines Verhalten beibehalten und anschließend Spezialverhalten ergänzen.
Wann ist Vererbung eine gute Modellierung?
Eine sprachlich plausible Einteilung ist noch keine gute Klassenhierarchie. Prüfe zwei Bedingungen:
- Ist-ein-Test: Ist die Unterklasse wirklich eine besondere Art der Oberklasse?
- Austauschbarkeit: Verhält sich jedes Unterklassenobjekt so, wie Verwender der Oberklasse es erwarten?
Komposition
Bei der Objektkomposition wird ein Objekt aus zusammenarbeitenden Objekten aufgebaut. Es hält beispielsweise eine Referenz auf ein anderes Objekt und nutzt dessen Methoden. So lässt sich eine Hat-Beziehung modellieren, ohne daraus eine Unterart zu machen. Gemeint ist hier der allgemeine Programmieransatz; in UML-Klassendiagrammen hat „Komposition“ eine engere Bedeutung mit zusätzlichen Regeln für die Zugehörigkeit der Teile.
Apfelkuchen ist ein Kuchen, aber kein Apfel. Die Zutaten gehören als Bestandteile zum Kuchen. Eine Vererbung von Apfel wäre deshalb unpassend.
Auch wechselnde Rollen sprechen gegen starre Unterklassen. Eine Person kann gleichzeitig Kunde und Mitarbeiter sein oder ihre Rolle wechseln. Verknüpfte Rollenobjekte bilden das flexibler ab als immer neue Klassen wie KundeUndMitarbeiter.
Das bekannte Modell Quadrat extends Rechteck wirkt zunächst logisch. Es kann aber problematisch werden, sobald ein veränderbares Rechteck Länge und Breite unabhängig setzen lässt. Gleicht ein Quadrat beim Setzen der Länge automatisch auch die Breite an, verhält es sich anders als ein Verwender eines allgemeinen Rechtecks erwartet. Das zeigt: Mathematische Einordnung allein garantiert noch keine sichere Subtypbeziehung im Programm.
Tiefe Hierarchien und unnötiges Überschreiben können Änderungen erschweren. Ändert sich eine zentrale Oberklasse, können viele Unterklassen betroffen sein. Halte Hierarchien deshalb überschaubar, kapsle interne Daten und beschreibe das erwartete Verhalten von Methoden eindeutig.
In einem fiktiven Geometrieprogramm kann die Klassenfolge Viereck → Rechteck → Quadrat gemeinsame Berechnungen nutzen. Ein Rechteck besitzt je zwei gleich lange gegenüberliegende Seiten. Deshalb ergibt sich bei Länge 10 cm und Breite 20 cm der Umfang aus 2 · 10 + 2 · 20 = 60, also 60 cm. Für die Fläche multiplizierst du Länge und Breite: 10 · 20 = 200, also 200 cm².
Beim Quadrat sind alle vier Seiten gleich lang. Bei Seitenlänge 30 cm ergibt sich der Umfang aus 4 · 30 = 120, also 120 cm; die Fläche aus 30² = 900, also 900 cm². Die Rechnungen zeigen mögliche Wiederverwendung. Ob die Vererbungsbeziehung auch bei veränderbaren Seiten korrekt bleibt, muss zusätzlich am erwarteten Verhalten geprüft werden.
Karteikasten
Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.
Alles auf einen Blick
- Vererbung
- Beziehung
- Oberklasse und Unterklasse
- Ist-eine-Art-von
- Austauschbarkeit
- Java
- `extends`
- `super(...)`
- `@Override`
- Spezialisierung
- übernehmen
- ergänzen
- überschreiben
- Laufzeit
- Polymorphie
- dynamische Methodenbindung
- Entwurfsprüfung
- Verhalten bewahren
- Komposition bei Hat-Beziehungen
- Hierarchien überschaubar halten
- Beziehung
Mit Google fortfahren