CodeGym /Kurse /JAVA 25 SELF /Fehler bei Vererbung und Methodenüberladung

Fehler bei Vererbung und Methodenüberladung

JAVA 25 SELF
Level 23 , Lektion 1
Verfügbar

1. Fehler bei der Vererbung

Vererbung ist eines der Grundprinzipien der OOP, aber auch eines der Themen, bei denen Anfänger am häufigsten in Fallen tappen. Lassen Sie uns die klassischen Fehler durchgehen und lernen, sie zu vermeiden.

Kein Aufruf des Konstruktors der Basisklasse (super(...))

Wenn Sie eine Unterklasse erstellen, ist wichtig zu bedenken: Die Basisklasse kann eine bestimmte Initialisierung über den Konstruktor erfordern. Wenn es in der Basisklasse keinen Standardkonstruktor (ohne Parameter) gibt, muss im Konstruktor der abgeleiteten Klasse zwingend der Konstruktor der Basisklasse explizit mit super(...) aufgerufen werden.

Beispielfehler:

class Animal {
    private String name;
    public Animal(String name) {
        this.name = name;
    }
}

class Dog extends Animal {
    // Fehler! Animal hat keinen parameterlosen Standardkonstruktor
    public Dog() {
        // super(); // Der Compiler fügt super() automatisch ein, aber es gibt keinen solchen Konstruktor!
    }
}

So beheben:

class Dog extends Animal {
    public Dog(String name) {
        super(name); // Alles gut!
    }
}

Kommentar:
Wenn es in der Basisklasse nur einen Konstruktor mit Parametern gibt, fügt der Compiler keinen parameterlosen Konstruktor automatisch hinzu. Das ist eine häufige Ursache für Kompilierungsfehler.

Versuch, von einer final-Klasse zu erben oder eine final-Methode zu überschreiben

In Java kann man eine Klasse oder eine Methode als final deklarieren. Das bedeutet:

  • Die Klasse kann nicht abgeleitet werden.
  • Die Methode darf in Unterklassen nicht überschrieben (override) werden.

Beispielfehler:

final class Cat {}

// Kompilierungsfehler!
class Tiger extends Cat { 
    // ...
}
class Animal {
    public final void sleep() {
        System.out.println("Zzz...");
    }
}

class Dog extends Animal {
    // Kompilierungsfehler!
    @Override
    public void sleep() {
        System.out.println("Dog is sleeping...");
    }
}

Kommentar:
Wenn Sie die Fehlermeldungen „cannot inherit from final“, „cannot override final method“ sehen – prüfen Sie die Modifikatoren!

Verstoß gegen das Liskovsche Substitutionsprinzip (Liskov Substitution Principle)

Klingt kompliziert, bedeutet in der Praxis aber: Ein Objekt einer Unterklasse muss sich wie ein Objekt der Basisklasse verhalten, ohne die Logik des Programms zu brechen. Ein häufiger Fehler ist, Methoden so zu überschreiben, dass sich die neue Klasse nicht wie erwartet verhält.

Beispiel:

class Bird {
    public void fly() {
        System.out.println("Ich fliege!");
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Pinguine fliegen nicht!");
    }
}

Wo liegt das Problem?
Code, der mit Bird arbeitet, erwartet, dass jeder Vogel fliegen kann. Wird ihm jedoch ein Penguin übergeben, kann das Programm abstürzen.

Besser:
In solchen Fällen sollte man die Hierarchie überdenken oder Schnittstellen/Komposition verwenden.

2. Fehler bei der Methodenüberladung (overloading)

Überladung bedeutet, dass es in derselben Klasse mehrere Methoden mit demselben Namen, aber unterschiedlichen Parametern gibt. Klingt einfach, doch auch hier lauern Fallen.

Überladen statt Überschreiben (Fehler in der Signatur)

Anfänger wollen oft eine Methode der Basisklasse überschreiben (override), ändern aber versehentlich deren Parameter. Am Ende entsteht eine Überladung, kein Override – und Polymorphie greift nicht!

Beispielfehler:

class Animal {
    public void makeSound() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    // Wollten überschreiben, haben aber überladen!
    public void makeSound(String extra) {
        System.out.println("Bark! " + extra);
    }
}

Problem:
Ein Aufruf dog.makeSound() ruft die Elternmethode auf, nicht Ihre neue.
Ein Aufruf dog.makeSound("loudly") ruft die überladene Methode auf, aber Polymorphie greift nicht!

Best Practice:
Verwenden Sie die Annotation @Override – wenn Sie sich in der Signatur vertun, weist der Compiler sofort darauf hin.

@Override
public void makeSound() { /* ... */ }

Unerwartetes Verhalten bei Überladung (automatische Typumwandlung, Mehrdeutigkeit von Aufrufen)

Java kann manchmal eine andere Methode „wählen“, als Sie erwarten, wenn die Parameter zu mehreren Überladungen passen.

public class OverloadDemo {
    public void print(int x) {
        System.out.println("int: " + x);
    }
    public void print(double x) {
        System.out.println("double: " + x);
    }
}

OverloadDemo demo = new OverloadDemo();
demo.print(5);     // int: 5
demo.print(5.0);   // double: 5.0
demo.print(5L);    // long -> double: double: 5.0

Problem:
Wenn Sie demo.print(5L) aufrufen, wählt Java print(double x) (da sich long besser in double als in int umwandeln lässt).
Wenn es Methoden mit Parametern der Typen Object, Integer, int gibt, kann ein Aufruf mit null einen Kompilierungsfehler auslösen: „reference to print is ambiguous“.

Gleicher Methodenname nur mit unterschiedlichem Rückgabetyp (Kompilierfehler)

In Java darf man nicht zwei Methoden mit demselben Namen und denselben Parametern deklarieren, die sich nur im Rückgabetyp unterscheiden!

public class Demo {
    // Kompilierungsfehler!
    public int foo() { return 1; }
    public String foo() { return "hello"; }
}

Erläuterung:
Für die Überladung zählt die Methodensignatur – also Name + Parameter. Der Rückgabetyp wird nicht berücksichtigt. Der Compiler kann nicht entscheiden, welche Methode aufgerufen werden soll.

3. Best Practices

Damit Sie bei Vererbung und Überladung nicht in Fallen tappen, halten Sie sich an diese Empfehlungen:

Verwenden Sie für zu überschreibende Methoden immer die Annotation @Override

Das erhöht nicht nur die Lesbarkeit, sondern schützt auch vor Signaturfehlern. Wenn Sie Parameter oder den Methodennamen versehentlich ändern, meldet der Compiler das sofort.

@Override
public void makeSound() {
    System.out.println("Bark!");
}

Unterscheiden Sie klar zwischen Überladung und Überschreibung

  • Überschreibung (override): Sie ändern das Verhalten der Elternmethode – die Signatur muss übereinstimmen.
  • Überladung (overload): Sie fügen eine neue Methode mit demselben Namen, aber anderen Parametern hinzu.

Tabelle zur Veranschaulichung:

Überladung (overloading) Überschreibung (overriding)
Wo In derselben Klasse/Hierarchie In der Unterklasse
Methodenname Identisch Identisch
Parameter Unterschiedlich Identisch
Rückgabewert Kann abweichen Muss identisch/kompatibel sein
Annotation Nicht erforderlich @Override empfohlen

Übertreiben Sie es nicht mit der Überladung

Hat eine Methode zu viele überladene Varianten, wird der Code unleserlich und verwirrend. Verwenden Sie besser Parameter-Objekte oder das Builder-Pattern, wenn es zu viele Varianten gibt.

4. Beispiel: System zur Erfassung von Haustieren

Angenommen, Sie erstellen ein einfaches System zur Erfassung von Haustieren. Sie haben eine Basisklasse Pet und die abgeleiteten Klassen Cat und Dog.

public class Pet {
    private String name;
    public Pet(String name) {
        this.name = name;
    }
    public void speak() {
        System.out.println(name + " gibt ein unverständliches Geräusch von sich.");
    }
}

public class Cat extends Pet {
    public Cat(String name) {
        super(name);
    }
    @Override
    public void speak() {
        System.out.println(getName() + " sagt: Miau!");
    }
    // Fehler: Es gibt keine Methode getName() in Pet!
}

Typischer Fehler:
Beim Versuch, eine Methode zu überschreiben, greifen wir auf eine Methode zu, die es in der Basisklasse nicht gibt. Besser einen Getter hinzufügen:

public class Pet {
    private String name;
    public Pet(String name) { this.name = name; }
    public String getName() { return name; }
    public void speak() { System.out.println(name + " gibt ein unverständliches Geräusch von sich."); }
}

Jetzt funktioniert alles korrekt, und wir können Polymorphismus verwenden:

Pet myPet = new Cat("Rudio");
myPet.speak(); // Rudio sagt: Miau!

5. Typische Fehler bei Vererbung und Überladung

Fehler Nr. 1: super(...) nicht aufgerufen.
Wenn es in der Basisklasse wichtige Logik im Konstruktor gibt, Sie diese aber nicht aufrufen, kann sich das Programm unerwartet verhalten oder gar nicht kompilieren.

Fehler Nr. 2: Die falsche Methode überschrieben.
Sie wollten das Verhalten der Elternmethode ändern, haben aber tatsächlich eine neue Methode mit ähnlichem Namen oder anderen Parametern hinzugefügt. Ergebnis: Die alte Methode arbeitet wie zuvor weiter, und Ihre neue wird von niemandem aufgerufen.

Fehler Nr. 3: Versuch, eine final-Methode zu überschreiben.
Java lässt das nicht zu – und gut so! Wenn Sie jedoch einen Kompilierungsfehler sehen, suchen Sie nach final.

Fehler Nr. 4: Methode bis zur Unkenntlichkeit überladen.
Wenn Sie 10 Varianten von calculate haben und selbst nicht mehr wissen, welche aufgerufen wird – Zeit für ein Refactoring.

Fehler Nr. 5: Liskov-Prinzip verletzt.
Wenn Ihre Unterklasse die Bedeutung des Verhaltens der Basisklasse ändert, kann die gesamte Architektur „aus dem Ruder laufen“. Wenn z. B. eine Klasse Shape eine Methode getArea() hat, eine Unterklasse BrokenShape aber -1 zurückgibt, kann das zu seltsamen Bugs führen.

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