Warum gibt es in Java keine Mehrfachvererbung, in C++ jedoch?
Ich mache mal meinen den Kommentar auf eine andere Antwort zur eigenen Antwort.
Das liegt am so genannten "Diamond Problem"
Nein, tut es nicht.
Das ist Gelaber aus einer Marketing-Kampagne, die sich Sun damals knapp eine Milliarde Dollar hat kosten lassen, um einer ziemlich überflüssigen Sprache namens Java eine Existenzberechtigung zu erflunkern.
Mehrfachvererbung ist Aggregation, Aggregation ist in Java nur mit Primitiven machbar und von Primitive kann man nicht erben.
Beschreibt folgende Ableitungshierarchie
struct A                          { virtual void f(){} }; 
struct B : public A               { virtual void f(){} }; 
struct C : public A               { virtual void f(){} }; 
struct D : public A, public B { using B::f; }; 
Was passiert, wenn D.f() aufgerufen wird?
Das ist kein Diamand-Problem! Das ist eine Ambivalenz, die jeder Compiler bemängelt, über das Keyword using geklärt oder explizit genannt wird:
D d; 
d.C::f(); // ruft die Implementierung von C 
d.B::f(); // ruft die Implementierung von B 
d.f();    // ruft entweder die Funktion oder using deklarierte f  
          // oder verweigert die Kompilierung. 
Das ist klar definiertes Verhalten, also definitiv kein Problem.
Ähnlich ist es mit Membervariablen: Da das nicht klar zu erkennen ist, verbietet Java (und viele andere Programmiersprachen) Mehrfachvererbung von vornherein.
Gleiches Problem, entweder die selbe Lösung oder man möchte gar kein zweites A.
Mehrfachvererbung zu Implementieren ist auch Programmieraufwand. Viele Sprachen - darunter Java - ermöglichen kein Const-Correctness. Mit einer der besten Erfindungen, die ich kenne. Es hat nur einen Nachteil: Man muss es in den Compiler einbauen. Das ist Arbeit. Der Erfinder von C# erklärte mal, dass Const-Correctness nutzlos sei, weil man es mit const_cast aushebeln kann. Das gilt auch für Haustüren. Trotzdem haben sich Haustüren in vielen Ländern durchgesetzt. Weil Leute, die mit dem Rammbock vor einer Haustür stehen und Lärm machen recht auffällig sind. const_cast lässt sich recht einfach suchen, blamen und dann kann man den Autor auch fragen, was denn der Scheiß soll, sich gewaltsam Zutritt in eine Software zu verschaffen, anstatt ein vernünftiges Design zu schaffen.
Aber ist das ein Problem mit mehreren Datensätzen aus A? Auch hier müssen wir jetzt klären, ob das A-Teil Teil von B ist und ein weiteres A-Teil von C und ob ein D zwingend zwei aus As bestehen muss oder ob A eigentlich Teil von D ist, also nur ein A in D enthalten sein soll, das B und C mitverwenden. Als Beispiel: Eine Kühlschrank (B) hat einen Kompressor (A), ein Gefrierschrank (C) hat einen Kompressor (A), wieviele Kompressoren braucht man für eine Kühl-Gefrierkombi, wenn man Kühlschränke und Gefrierfach schon fertig designed hat?
class Kompressor 
{} 
 class Kühlschrank : public Kompressor 
{} 
 class Gefrierfach : public Kompressor 
{} 
 class Kühlgefrierkombi 
  : public Kühlschrank 
  , public Gefrierfach    // Diamant-Problem: Zwei unabhängige Kompressoren 
{} 
Nun sieht die Sache in Java ohne Mehrfachvererbung natürlich viel besser aus:
class Kühlgefrierkombi 
{ 
  public Kühlschrank mKühlschrank; 
  public Gefrierfach mGefierfach; 
} 
Ähh… ne, doch nicht, hier sind auch zwei Kompressoren drin, die dann mit mKühlschrank.Kompressor und mGefrierfach.Kompressor angesprochen werden, statt in C++ mit Kühlschrank::Kompressor und Gefrierfach::Kompressor. Irgendwie hat uns das Verbot der bösen Mehrfachvererbung hier nicht weiter gebracht.
Mehrfachvererbung ist Aggregation mit implizierter Einbindung in den Namensraum. Inhaltlich tut sich da nix. Weswegen Mehrfachvererbung oder Diamanten kein Problem sind und Java hier auch keine Lösung hat.
Bleibt die Frage, wie wir jetzt den doppelten Kompressor aus der Kombi rausbekommen.
class Kühlgefrierkombi  
  : public Kühlschrank 
  , public Gefrierfach 
  , virtual public Kompressor 
{}; 
Das lässt sich in C++ mit virtueller Vererbung klären. C++ kommt also mit fertiger Lösung. Das ist auch nicht neu oder entwickelt worden, weil die Java-Entwicker auf das Problem hingewiesen hätten. Das gibt's schon länger als Java. Das Diamant-Problem ist kein Problem, sondern ein Feature, was man nicht immer haben möchte. Das war auch den Java-Entwicklern nicht neu. Aber man muss es nunmal erklären, wenn man einen Java-Compiler schreibt. Das ist Arbeit. Und vor allem definitiv Aggregation. Und das kann Java ja nicht.
Es wurde erst zum Problem, weil sich nunmal sehr schlecht mit "Wir sind dazu nicht in der Lage" werben lässt. Werbung macht man, in dem man Problemlösungen verkauft. Dafür braucht man Probleme.
Man hätte es assoziativ lösen können, denn nichts anderes ist virtuelle Ableitung. Ableitung ist wirklich keine Magie. Also die Lösung für das Diamant-"Problem", die C++ auch anbietet, die Java können könnte, kann Java auch nicht.
Und an der Stelle muss man sagen, dass das Diamant-Problem dann ein Problem ist, wenn die softwaretechnische Ausbildung fehlt, bzw. die Ausbildung solche Konstrukte in C++ zu formulieren. Das ist also Bildungsproblem des Entwicklers. Bildung kostet Geld. Das Versprechen von Sun war, billigere Entwickler durch weniger erforderliche Ausbildung. Wenn ich den Leuten nur sagen muss "Diamanten sind böse", muss ich ihnen nicht erklären, was man bei solchen Designs beachten muss. Bis sich herausstellt, dass die oben stehende Java-Lösung nunmal trotzdem zwei Kompressoren verbaut. Kurz: Das Problem ist trotzdem da, ich muss jetzt massiv umbauen, weil man es in Java nunmal nicht über Vererbung formulieren kann. An der Stelle stellt man dann fest, dass Java-Entwicklung auch nicht billiger ist, sondern eher teurer.
Bildung ist nicht die Grundlage von Java, sondern im Gegenteil. Zu was führt das? Ich hatte als Kind mal einen Sticker auf dem stand "Geistige Windstille führt zu operativer Hektik". Nochmal zum Mitschreiben: Das neue Produkt/Feature Kühlgefrierkombi mit einem Kompressor soll ja trotzdem auf den Markt. Und ist in C++ ja über virtuelle Vererbung schnell nachrüstbar, in Java bauen Entwickler seit Dekaden aus Kühlschränken und Gefrierfächern Kompressoren um, verklausuliere virtuelle Vererbung über Interfaces und schaffe damit Redundanz und mehr Testfälle. Also viel mehr Arbeit für alle Seiten.
Dazu ein Beispiel aus der Praxis: Ich habe mit Mehrfachvererbung und virtueller Vererbung mal ein Daten-Austauchformat beschrieben, an dem die Konkurrenz mit Java ein Jahr Vorsprung hatte. Nach 6 Monaten haben wir denen Bug-Reports geschickt, weil es für uns einfacher war, denen zu sagen, wo sie sich nicht an den Formatstandard halten, als unsere fertige Laderoutine so anzupassen, dass sie an deren fehlerhaften Dateien ruminterpretiert. Ich konnte aus der technischen Beschreibung 600 Klassen C++-Code generieren, weil C++ über das Vokabular verfügt, dieses Format 1:1 zu beschreiben. Und in Java war das halt sehr viel fehleranfällige Handarbeit.
Warum dann also keine Mehrfachvererbung in Java?
Mehrfachvererbung ist per Default aggregativ. Das ist in Java nicht möglich, nicht weil man den Entwickler vor Bildung zu schützen möchte oder den Software-Hersteller vor unqualifizierten Entwicklern, sondern es ist technisch mit Java nicht sinnvoll umsetzbar. Man verzichtet ja auch nicht auf Pointer-Arithmetik, weil man damit Algorithmen um den bösen Faktor 2 beschleunigt, sondern weil es technisch in Java nicht umsetzbar ist, ohne Java um einen je nach Speicherbedarf zunehmenden Faktor zu verlangsamen und damit unbrauchbar zu machen. Schicker klingt natürlich "Pointer-Arithmetik ist gefährlich".
Was ist das Problem? Die Garbage Collection. Um die Garbage Collection in ihrer Komplexität halbwegs unter Kontrolle halten zu können, darf nur ein einziger Zeiger pro Objekt existieren und es muss genau ein Zeiger pro Objekt existieren, den man dann referenzieren kann. Darum sind Klasses auch immer "Referenztypen", weil man in Java eben nicht ohne Zeiger programmieren kann, auch wenn gerne behauptet wird, dass man in Java gar keine Zeiger hat.
In C++ ist keinen Pointer zu verwenden das Defaultverhalten, was Java gar nicht kennt und auch nicht formulieren kann.
Beispiel:
C++ : Point2d * pp = new Point2d( 1, 2 );  // new liefert einen Pointer. 
Java: Point2d   pp = new Point2d( 1, 2 );  // Man beachte: kein *, aber trotzdem new 
Der * in C++ steht für Zeiger. Lassen wir ihn in C++ halt mal weg, dann haben wir keinen Zeiger sondern ein Objekt, also brauchen wir auch kein new.
C++ : Point2d    p = Point2d( 1, 2 );      // auch kein Stern, aber auch kein new. 
 oder kurz 
 C++: Point2d p( 1, 2 ); 
"Ohne Pointer" ist in Java nicht ausdrückbar, weil genau 1 Zeiger für die GarbageCollection da sein muss, nicht mehr, nicht weniger. Und wenn der Zeiger null ist,
Point2d p; 
p.x = 1; 
dann kommt auch eine Null-Pointer-Exception. Denn die Referenz p ist ja gültig. Sie referenziert auf den nicht initialisierten Zeiger. Der identische Code läuft in C++ übrigens fehlerfrei, weil C++ keine "Referenztypen" für sein Speichermodell braucht.
Mehrfachvererbung bedeutet mehrere Objekte (also auch mehrere Zeiger) innerhalb eines Objektes, denn um ein Teilobjekt an eine Funktion zu übergeben, muss ich ja in der Lage sein, den vorab liegenden anderen Teil des Objektes zu überspringen. Möchte ich einer Funktion das Gefrierfach der Kombi zeigen, darf ich nicht auf das komplette Aggregat zeigen, sondern muss unter den Kühlschrank zeigen, wo halt das Gefrierfach sitzt. Das ist ein anderer Zeiger, der eine Abhängigkeit zum Zeiger auf das Aggregat hat. Dieses Gefrierfach darf ich nicht löschen, weil sonst meine Kühlgefrierkombi kaputt geht. Oder das Gefrierfach löst sich mitten im Gebrauch in Luft auf, wenn ich anderswo das Aggregat lösche. Woher soll die GC wissen, dass von der Kombi noch irgendwer sich für das Gefrierfach interessiert - das ist für die GC ein anderer Zeiger. Oder die Verwaltung wird aufwendiger und damit die Software noch langsamer.
Die GC löst nur wenige Probleme, aber sie erschafft viele Neue. Eine GarbageCollection kann keine Aggregation verwalten.
Ebenso ist Pointer-Arithmetik mit dem Java-Speichermodell nicht realisierbar. Ein Zeiger, der in einem Array nicht auf das erste Element zeigt, darf nicht gespeichert werden, weil sonst die GC nicht weiß, ob das Array noch in Benutzung ist. Kann man Zeiger zwischenspeichern, könnte es ja eine beliebige Anzahl von Referenzen des Objektes geben und die müssen alle verwaltet werden und das Objekt könnte nur gelöscht werden, wenn alle 0 mal benutzt werden. Das ist aufwendig. Also noch langsamer als die GC eh schon ist.
Mit Gefahr, Diamant-Problem oder sonstwas hat das alles nix zu tun. Eine Sprache, die eine Garbage Collection anbietet, bekommt die nunmal nicht geschenkt. Am Ende ist alles ein Kompromis zwischen Feature und den Kosten für das Feature.
Die Garbage Collection in C++ nennt sich RAII. Ihr Aufruf-Operator ist }, das ist die Klammer, die gar nicht so zufälligerweise an am Ende von jedem Scope steht und in klar definierter Weise den Müll beseitigt, der gerade in diesem Scope verworfen wurde.
Sie ist quasi kostenlos. Sie sammelt aber halt auch keinen Müll. Ich muss also auch keine Müllkippe verwalten. Richtet man sein Softwaredesign darauf aus, braucht man auch keine Zeiger, weil sie mit Values statt mit Referenzen arbeitet. Ist auch uralt. Man könnte auch sagen, C++ hält hier kommentarlos die Versprechen, die Java später erst gemacht und dann gebrochen hat.
Was Java als Programmiersprache (unter anderem) zu einer ziemlichen Fehlkonstruktion macht.
Der mangelnde Fähigkeit Aggregation zu erlauben ist übrigens der Grund, warum ich Java für tot erkläre, seitdem ich Java vor rund über 20 Jahren im Studium gelernt habe. Und zwar während ich es lernte und den Prof genau zu Aggregationen befragte. "Wie das geht nicht!?" Wieso wird das als der neue heiße Scheiß verkauft, wenn die Basics nicht gehen!? Wie soll das dauerhaft eine Zukunft haben? Es wird immer verlieren, wenn man Performance braucht.
Aggregation wird mit der wachsenden Bedeutung von CPU-Caches immer wichtiger und die Verwendung von Java damit zur immer schlechteren Wahl, weil es Software ausbremst und damit den Stromverbrauch erhöht. Das gilt besonders in Rechenzentren und in Mobilgeräten, also genau da, wo Java noch populär ist.
Diesen Monat ist Java bei Tiobe auf Platz 4 gerutscht, hinter C++. Die werden sich die nächsten Monate, vielleicht noch Jahre ums Treppchen streiten, aber Java wird definitiv verlieren und verschwinden, weil es einfach immer nur ein großes Versprechen, aber noch nie eine gute Idee war. Hype hin oder her, es ist tot, weil es die massiven Designfehler auch nicht mit neuen Versionen beheben kann.