- 1 Section
- 10 Lessons
- unbegrenzt
- Java Fortgeschritten10
- 1.1OOP in Java: Klassen, Kapselung, Konstruktoren
- 1.2Vererbung in Java
- 1.3Polymorphismus und Methodenüberschreibung
- 1.4Interfaces und abstrakte Klassen
- 1.5Collections: ArrayList, HashMap, LinkedList
- 1.6Generics und Typsicherheit
- 1.7Streams und Lambda-Ausdrücke
- 1.8Dateioperationen: IO und NIO
- 1.9Unit-Tests in Java: JUnit 5
- 1.10Praxisprojekt: Anwendung mit OOP und Tests
Vererbung in Java
Vererbung ist das zweite große OOP-Konzept nach der Kapselung aus L1. Die Idee: eine Klasse erbt Felder und Methoden von einer anderen Klasse und erweitert sie. Statt Code zu kopieren, baust du auf bestehenden Klassen auf.
Klassisches Beispiel: Auto und LKW haben vieles gemeinsam – Marke, Farbe, beschleunigen. Aber ein LKW hat zusätzlich eine Ladekapazität. Statt zwei Klassen unabhängig zu schreiben, lässt du LKW von Auto erben und ergänzt nur das Neue. Diese Lektion zeigt dir, wie das in Java geht – mit extends, super(), dem Modifier protected und der wichtigen Klasse Object, von der jede Java-Klasse implizit erbt.
1) Die Grundidee
Vererbung modelliert eine „ist-ein"-Beziehung:
- Ein LKW ist ein Auto (mit zusätzlichen Eigenschaften)
- Ein Hund ist ein Tier
- Eine SparBank ist ein Konto
In Java drückst du das mit extends aus:
public class LKW extends Auto { private int ladekapazitaet; }
LKW ist jetzt eine Unterklasse (englisch subclass) von Auto – auch abgeleitete Klasse oder Kindklasse genannt. Auto ist die Oberklasse (superclass, Basisklasse, Elternklasse).
2) Was wird vererbt?
Die Unterklasse bekommt:
- Alle öffentlichen und geschützten Felder der Oberklasse (public, protected, default im selben Package)
- Alle öffentlichen und geschützten Methoden
- Nicht vererbt:
privateFelder und Methoden (Implementierungsdetails der Oberklasse) und Konstruktoren
Konsequenz: die Unterklasse kann auf öffentliche und geschützte Mitglieder der Oberklasse zugreifen, als wären es eigene. Private Felder bleiben für die Unterklasse unerreichbar.
3) Vererbungs-Baum
Das Beispiel grafisch:
LKW erbt von Auto erbt von Fahrzeug erbt implizit von Object. Jeder Vorfahr trägt seine Eigenschaften bei.4) Beispiel: Auto und LKW
So sieht das Ganze in Code aus:
public class Auto { protected String marke; protected int baujahr; public Auto(String marke, int baujahr) { this.marke = marke; this.baujahr = baujahr; } public void starten() { System.out.println(marke + " startet"); } } public class LKW extends Auto { private int ladekapazitaet; public LKW(String marke, int baujahr, int ladekapazitaet) { super(marke, baujahr); // Konstruktor der Oberklasse aufrufen this.ladekapazitaet = ladekapazitaet; } public void beladen(int kg) { System.out.println(marke + " lädt " + kg + " kg"); } }
Nutzung:
LKW lkw = new LKW("MAN", 2020, 7000); lkw.starten(); // "MAN startet" – geerbt von Auto lkw.beladen(5000); // "MAN lädt 5000 kg" – eigene Methode
5) Das Schlüsselwort super
super verweist auf die Oberklasse. Es hat zwei wichtige Verwendungen:
super(...)im Konstruktor: ruft einen Konstruktor der Oberklasse auf. Muss als erste Anweisung im Konstruktor der Unterklasse stehen.super.methode(): ruft eine Methode der Oberklasse auf – nützlich, wenn die Methode in der Unterklasse überschrieben wurde, du aber zusätzlich das Original ausführen willst
public class LKW extends Auto { @Override public void starten() { super.starten(); // erst Original ausführen System.out.println("Brummen des Diesels"); } }
Wenn du super(...) im Konstruktor weglässt, fügt der Compiler automatisch super() ohne Argumente ein. Das funktioniert nur, wenn die Oberklasse einen parameterlosen Konstruktor hat.
6) UML-Notation für Vererbung
# steht für protected – diese Felder sind für Unterklassen sichtbar, aber nicht für fremden Code.7) protected genauer betrachtet
Der Modifier protected ist speziell für Vererbung gemacht. Er gibt drei Gruppen Zugriff:
- der eigenen Klasse
- allen Klassen im selben Package
- allen Unterklassen, auch wenn sie in anderen Packages liegen
Konsequenz: ein protected Feld in Auto ist in jeder Unterklasse direkt nutzbar. Aus dem fremden Code außerhalb der Vererbungshierarchie aber nicht. Das ist die übliche Stufe für Daten, die Unterklassen brauchen.
private + öffentliche Getter/Setter als protected-Felder. protected bindet Unterklassen an interne Details der Oberklasse, was spätere Änderungen erschwert. Nur einsetzen, wenn die Vererbungs-Schnittstelle bewusst geplant ist.
8) Methoden überschreiben (Override)
Die Unterklasse kann geerbte Methoden überschreiben (engl. override) – also dieselbe Signatur, andere Implementierung:
public class Auto { public void hupen() { System.out.println("Beep beep"); } } public class LKW extends Auto { @Override public void hupen() { System.out.println("BWAAAH"); } }
Die @Override-Annotation ist optional, aber dringend empfohlen: der Compiler prüft dann, dass tatsächlich eine Methode der Oberklasse existiert, die überschrieben wird. Ein Tippfehler im Methodennamen wird sofort erkannt, statt unbemerkt eine neue Methode zu definieren.
Mehr zu Polymorphismus und dynamischer Methodenauswahl gibt es in L3 (Polymorphismus) – Vererbung ist die Voraussetzung dafür.
9) Override-Regeln
Beim Überschreiben gelten strenge Regeln:
- Selbe Signatur: Methodenname, Parameter-Typen und -Reihenfolge müssen identisch sein
- Rückgabetyp: identisch oder ein kovarianter Rückgabetyp (Subtyp des Originals)
- Sichtbarkeit nicht reduzieren: eine
publicMethode der Oberklasse kann in der Unterklasse nicht zuprivatewerden – wohl aber umgekehrt - Keine zusätzlichen Checked Exceptions: die Unterklasse darf nicht mehr oder breitere Exceptions werfen als das Original
Verstößt du dagegen, lehnt der Compiler die Klasse ab. @Override macht solche Fehler sofort sichtbar.
10) Overloading vs. Overriding
Zwei ähnliche Begriffe, die oft verwechselt werden:
| Overloading (Überladen) | Overriding (Überschreiben) |
|---|---|
| Mehrere Methoden mit gleichem Namen, unterschiedlichen Parametern | Methode der Oberklasse mit gleicher Signatur in der Unterklasse neu implementiert |
| In derselben Klasse | Über Klassen-Hierarchie hinweg |
| Compile-Zeit-Entscheidung | Laufzeit-Entscheidung (dynamic dispatch) |
Beispiel: print(int) und print(String) |
Beispiel: toString() in eigenen Klassen überschreiben |
11) Die Klasse Object
Jede Klasse in Java erbt implizit von java.lang.Object – auch wenn du das nicht hinschreibst. Das hat Konsequenzen: Object definiert Methoden, die jede Klasse hat:
toString()– String-Repräsentationequals(Object)– GleichheitsprüfunghashCode()– Hashwert für HashMap/HashSetgetClass()– Laufzeitklasse abfragenclone()– flaches Kopieren (selten direkt genutzt)
Standardimplementierungen sind meist nicht hilfreich – toString() liefert z. B. nur einen kryptischen Hashcode. Deshalb überschreibt man sie in eigenen Klassen häufig (siehe L1).
12) Konstruktor-Kette
Beim new einer Unterklasse wird zuerst der Konstruktor der Oberklasse ausgeführt, dann der der Unterklasse – über alle Vererbungsebenen hoch bis zu Object:
new LKW(...)13) final – Vererbung verbieten
Manchmal soll eine Klasse nicht erweitert werden, eine Methode nicht überschrieben werden. Das Schlüsselwort final macht das explizit:
public final class String { ... } // java.lang.String ist final – niemand kann String erweitern public class Tier { public final void atmen() { ... } // Diese Methode darf nicht überschrieben werden }
Anwendungsfälle:
- Sicherheit/Stabilität: wichtige Klassen sollen nicht durch fremde Unterklassen verändert werden
- Performance: der Compiler kann final-Methoden manchmal inline einbinden
- Klare Schnittstelle: zeigt, dass die Klasse als fertiges Endprodukt gedacht ist
14) Komposition statt Vererbung
Vererbung ist mächtig, aber oft missbraucht. Eine bewährte Regel der OOP-Welt:
„Bevorzuge Komposition gegenüber Vererbung."
Das heißt: statt eine Klasse von einer anderen erben zu lassen, gibst du ihr ein Objekt der anderen Klasse als Feld. Beispiel:
// VERERBUNG (oft starr und gekoppelt) public class Auto extends Motor { ... } // KOMPOSITION (flexibler) public class Auto { private Motor motor; // hat-einen Motor public Auto(Motor motor) { this.motor = motor; } }
Komposition lässt sich zur Laufzeit ändern (Motor austauschen), erlaubt mehrere „Bestandteile" (Motor, Getriebe, Bremse) und macht den Code testbarer. Vererbung nur dann nutzen, wenn die „ist-ein"-Beziehung tatsächlich korrekt ist und stabil bleibt. Mehr dazu in K48 (OOP-Konzepte) und bei Design Patterns (K49).
15) Häufige Fehler
- Konstruktor der Oberklasse nicht aufrufen: wenn die Oberklasse keinen parameterlosen Konstruktor hat, brauchst du ein explizites
super(...) - private Felder direkt zugreifen wollen: nicht möglich, Unterklassen sehen sie nicht. Stattdessen geerbte Getter benutzen
- @Override vergessen bei Methoden mit Tippfehler: aus dem Override wird eine neue Methode, Verhalten ändert sich nicht wie erwartet
- Vererbung zu tief: drei Ebenen tief ist die Faustgrenze. Tiefere Hierarchien werden kaum noch wartbar
- Vererbung als „Code sparen" missbraucht: nur weil zwei Klassen ähnliche Methoden haben, ist Vererbung nicht zwingend richtig. Frage: ist eine wirklich „ein" anderer?
- Mehrfachvererbung erwartet: Java hat keine Mehrfachvererbung von Klassen – nur eine direkte Oberklasse. Mehrfache Schnittstellen gehen aber, siehe L4
Zusammenfassung
Vererbung in Java erlaubt einer Klasse, Felder und Methoden einer anderen zu übernehmen und zu erweitern – ausgedrückt mit class Sub extends Super. Die Unterklasse erbt alle public und protected Mitglieder, aber keine private Mitglieder und keine Konstruktoren. Mit super(...) ruft sie den Konstruktor der Oberklasse auf, mit super.methode() die ursprüngliche Implementierung einer überschriebenen Methode. Beim Überschreiben (@Override) müssen Signatur und Sichtbarkeitsregeln eingehalten werden. Jede Klasse erbt implizit von Object – daher haben alle Klassen Methoden wie toString() und equals(). Mit final verhindert man Vererbung oder Override. Faustregel: lieber Komposition statt Vererbung, wenn beides möglich ist.
