Strukturierte Mehrfachvererbung in JAVA I – Vererbungsketten, Interface Defaults

Es gibt (zum Glück!) keine Mehrfachvererbung in JAVA.
ABER man kann sich mit sog. Vererbungsketten helfen.

Die beispielhaften Basisklassen A und B:

class A{
    public void getA(){...}
}
class B{
    public void getB(){...}
}

Will man nun eine Klasse C schreiben, die A und B erweitert,
erhält man eine Fehlermeldung vom Compiler.

class C extends A,B{
    ...
}

Klasse C würde in C++ so aussehen: 

class C extends A,B{
    public void getA(){...}
    public void getB(){...}
}

Der Grund, warum JAVA Mehrfachvererbung verbietet, liegt darin, dass eine Mehrdeutigkeit entsteht, wenn man folgende Änderungen an Klasse A und B vornähme:

class A{
    public void getA(){...}
    public void getX(){...}
}
class B{
    public void getB(){...}
    public void getX(){...}
}

Klasse C in C++ : 

class C extends A,B{
    public void getA(){...} //A.getA()
    public void getB(){...} //B.getB()
    public void getX(){...} //A.getX()
    public void getX(){...} //B.getX()
}

Eine Klasse C, die A und B erweitert, müsste sich nun „entscheiden“, welche der beiden getX()-Methoden aufgerufen werden soll – die von A oder die von B ?

Das ist ein Problem.

In C++ ist Mehrfachvererbung erlaubt. Dort wird das Mehrdeutigkeitsproblem entweder so gelöst, dass man explizit angibt, welche Basisklasse genutzt werden soll. Oder aber man bedient sich virtueller Klassen.

JAVA will durch das Verbot solcher Szenarien grundlegend sauberen Code sicherstellen und potentiell schwer auffindbare Fehler vermeiden.

Man kann sich jedoch helfen, wenn man die Funktionalität trotzdem für unvermeidlich hält!

Man könnte z.B. Klasse B so verändern, dass sie von A erbt:

class A{
    public void getA(){...}
    public void getX(){...}
}
class B extends A{
    public void getB(){...}
    public void getX(){...}
}
=>
class C extends B{
    public void getA(){...}
    public void getB(){...}
    public void getX(){...} //B.getX
}

Klasse B überschreibt die getX()-Methode von A. C kann also nur noch auf die getX()-Methode von B verweisen.
Damit ist die Eindeutigkeit des Codes gewährleistet.

Die Lösung, die in JAVA umgesetzt ist, bietet viele Vorteile gegenüber echter Mehrfachvererbung wie man sie aus C++ kennt.

  • man wird gedrängt, die Vererbungshierarchie besser zu planen
  • die Klassenstruktur wird übersichtlicher
  • man muss Methoden einer Klasse feiner aufteilen
  • dadurch enthält jede einzelne Klasse weniger Code
  • dadurch wiederum steigt die Wartbarkeit des Codes
  • man erhält mehrere Klassen, die man modular in anderen Projekten einsetzen kann
  • es lassen sich koomplexe Vererbungshierarchien erzeugen, die jedoch übersichtlich bleiben

Alles in Allem sind Vererbungsketten in JAVA eine feine Sache und sollte von jedem Entwickler, ob der eigenen Fehleranfälligkeit, mit offenen Armen empfangen werden.

Default-Methoden in Java 8 Interfaces

Tja, man könnte meinen, die JAVA-Entwickler sahen sich zu einem Schritt gezwungen, Mehrfachvererbung doch noch einzubauen. Aber es gibt andere Gründe dafür: Java 8 Default Methods – Why?

Und auch dabei kann man keine Methoden mehrfach überladen, sondern nur fehlende Implementierungen mit default-Implementierungen ersetzen lassen. Beim Versuch der Mehrfachüberladung beschwert sich der Compiler und versagt den Dienst.

Weiter zu Teil II: Strukturierte Mehrfachvererbung in JAVA II – Methoden durch Interfaces ersetzen

 

15 Gedanken zu “Strukturierte Mehrfachvererbung in JAVA I – Vererbungsketten, Interface Defaults

  1. Witzig, genau das gleiche Problem hatte ich letztens in PHP. Da funktioniert der Spass genau so. Toller Beitrag.

  2. Wenn ich Klasse B nun zwischen A und C einspanne, habe ich mir doch eine vertikale Abhängigkeit eingekauft, die es in den zwei Ebenen des C++ Beispiels nicht gibt.

    Von allen ‚getX()‘ Änderungen die ich in A vornehme, muss ‚B‘ wissen, sonst knallts in Klasse C – und das klingt nicht nach »man erhält mehrere Klassen, die man modular in anderen Projekten einsetzen kann«.

    🙂

  3. Danke für den Kommentar. Augenscheinlich ist mir hier ein Formulierungsfehler unterlaufen. Selbstverständlich lassen sich die Klassen nicht vollständig losgelöst auskoppeln.

    Will man C verwenden, braucht es natürlich die Abhängigkeiten bis hinunter zu A.

    Will man B nutzen, braucht man immer noch A, jedoch nicht C. Das war der Punkt, den ich hier eigentlich aufzeigen wollte.

    „Modul“ würde ich nun wohl eher als Teilhierarchie bezeichnen.

  4. Wohl wahr. der Hauptgrund, den Artikel zu schreiben war code eines anderen Entwicklers, der ca 2500 Zeilen Code in eine Klasse geworfen hat.

    Die genannte Methode ist IMHO ein guter Weg, um den Code innerhalb einer Klasse zu reduzieren.

  5. Sorry, aber der artikel ist nicht ganz richtig da das oben genannte Beispiel keine Mehrfachvererbung ist. Es handelt sich eher um einen Klassenbaum.

    Im übrigen sind 2500 Zeilen nicht viel ich kenne 5 stellige werte. Über die Qualität des Qäälcodes möchte ich nichts schreiben …

  6. Danke für deinen Kommentar!

    wenn ich mal zeit habe, werde ich alle von den Lesern genannten Korrekturen (inkl. klassenbaum) einarbeiten.

    kurz zu den anderen Punkten:
    – 500 Zeilen Quellcode sind einfach überschaubarer als 2500. Allein aufgrund der Zeilenzahl. Ein weiterer Aspekt ist, dass die Kleinteiligkeit praktisch für die Entwicklung im Team ist.
    – für einen Proof of Principle reicht die Codequali allemal.

  7. Wie hier beschrieben ist das wohl der einzige Weg, doppelten Code zu vermeiden. Aber auch dieser Weg hat seine Grenzen. Nämlich dann ,wenn:
    class A extends ÄtschA
    class B extends ÄtschB, also beide Klassen schon abgeleitet sind.
    Nun muss ich doch in A und B Methoden doppelt implementieren, da beide Klassen ja bereits abgeleitet wurden und eine Mehrfachvererbung ja nicht möglich ist.
    In meinem aktuellen konkreten Fall heißt das: in 50 Klassen jeweils 20 Methoden mit absolut identischem Inhalt implementieren. Und wenn sich etwas ändert: in 50 Klassen nachziehen. Mann oh Mann…ich habe gerade etwas Frust…

  8. Hi,

    ich glaube, die verwendest den Begriff „Mehrfachvererbung“ falsch. oder ich hab deinen kommentar nicht verstanden 😉

    poste doch bitte einfach mal ein bisschen code.

    nico

  9. Ich habe das gleiche Problem wie Kim. Und zwar sieht das zum Beispiel folgendermaßen aus:

    // allgemein
    class Fahrzeug {…}

    // allgemein
    class Motorrad extends Fahrzeug {…}

    // BMW-spezifisch
    class BmwFahrzeug extends Fahrzeug {…}

    // BMW-spezifisch
    class BmwMotorrad extends BmwFahrzeug, Motorrad {…}

    Konkret geht es dabei darum, dass sich die Klassen Fahrzeug und Motorrad in einer eingebunden Library befinden, welche nicht über die für BmwFahrzeug und BmwMotorrad benötigten Imports verfügt. Ich hätte für ein BmwMotorrad also gerne die Methoden eines gewöhnlichen Motorrads und gleichzeitig alle Features, die mir ein BmwFahrzeug bietet. Gleichzeitig möchte ich mir offen halten, auch mal eine andere Marke als nur BMW hinzuzufügen, die ebenfalls von den allgemeinen Objekten erben soll. Gibt es dafür einen schönen Lösungsweg ohne copy/paste?

  10. hallo daniel,

    einen „schönen“ lösungsweg gibt es leider nicht.

    einzige mir bekannte variante ist, das Ganze über interfaces abzudecken.

  11. Also um bei bereits vererbten klassen doppelte methoden/source zu vermeiden:
    Componenting nutzen.

    Also z.B. gucken ob bestimmte funktionen in eine selbstsändige klasse können.
    Man bedenke eine motorklasse für die in ejdem fahrzeug ein Objekt erstellt wird, welche aber nicht zur hierarchie gehört

    MFG
    Memnarch

  12. Die Frage ist, wozu man vererben sollte, wenn Modularität gefordert ist? In dem Fall ist, meiner Meinung nach, ganz klar einfache Instanzierung gefragt. Vererbung sollte, schon von der Logik her, angewendet werden, wenn die erweiternde Klasse in einer direkten Abhängigkeit zur Superklasse steht. Mehrfachvererbung ist in vielen Fällen einfach große Grütze und schlechter Stil.

  13. Hi,
    generell eine gute Idee hinter die ich auch schon mal gekommen bin.
    Grenzen hat das Szenario aber dann, wenn du von zwei Klassen erben möchtest, auf die du keinen Einfluss hast (aus Bibliotheken etc.). Denn da kann ich nicht so einfach B von A erben lassen, damit ich in C beides habe. Oder sehe ich da was falsch?

    Ich bin kein JAVA Pro, lasse mich also gerne belehren 😀

    lg

    Artur

Schreibe einen Kommentar