Dies ist ein weiteres Problem des Sprachdesigns, das "offensichtlich eine gute Idee" zu sein scheint, bis Sie anfangen zu graben und feststellen, dass es tatsächlich eine schlechte Idee ist.
Diese Mail hat viel zu diesem Thema (und auch zu anderen Themen). Es gab mehrere Designkräfte, die zusammen kamen, um uns zum aktuellen Design zu bringen:
- Der Wunsch, das Vererbungsmodell einfach zu halten;
- Die Tatsache, dass Sie, wenn Sie die offensichtlichen Beispiele hinter sich lassen (z. B. sich
AbstractList
in eine Schnittstelle verwandeln ), feststellen, dass das Erben von equals / hashCode / toString stark an die einzelne Vererbung und den Status gebunden ist und die Schnittstellen mehrfach vererbt und zustandslos sind.
- Dass es möglicherweise die Tür für einige überraschende Verhaltensweisen geöffnet hat.
Sie haben bereits das Ziel "Einfach halten" angesprochen. Die Vererbungs- und Konfliktlösungsregeln sind sehr einfach gestaltet (Klassen gewinnen über Schnittstellen, abgeleitete Schnittstellen gewinnen über Superschnittstellen und alle anderen Konflikte werden von der implementierenden Klasse gelöst.) Natürlich könnten diese Regeln angepasst werden, um eine Ausnahme zu machen, aber Ich denke, Sie werden feststellen, wenn Sie an dieser Schnur ziehen, dass die inkrementelle Komplexität nicht so gering ist, wie Sie vielleicht denken.
Natürlich gibt es einen gewissen Nutzen, der mehr Komplexität rechtfertigen würde, aber in diesem Fall ist er nicht vorhanden. Die Methoden, über die wir hier sprechen, sind equals, hashCode und toString. Bei diesen Methoden geht es ausschließlich um den Objektstatus, und es ist die Klasse, die den Status besitzt, nicht die Schnittstelle, die am besten bestimmen kann, was Gleichheit für diese Klasse bedeutet (insbesondere, da der Vertrag für Gleichheit ziemlich stark ist; siehe Effektiv) Java für einige überraschende Konsequenzen); Interface-Writer sind einfach zu weit entfernt.
Es ist einfach, das AbstractList
Beispiel herauszuholen . Es wäre schön, wenn wir AbstractList
das Verhalten loswerden und in die List
Benutzeroberfläche einfügen könnten . Wenn Sie jedoch über dieses offensichtliche Beispiel hinausgehen, gibt es nicht viele andere gute Beispiele. An der Wurzel AbstractList
ist für die Einzelvererbung ausgelegt. Schnittstellen müssen jedoch für Mehrfachvererbung ausgelegt sein.
Stellen Sie sich außerdem vor, Sie schreiben diese Klasse:
class Foo implements com.libraryA.Bar, com.libraryB.Moo {
// Implementation of Foo, that does NOT override equals
}
Der Foo
Autor betrachtet die Supertypen, sieht keine Implementierung von Gleichheit und kommt zu dem Schluss, dass er nur Gleichheit von erben muss, um Referenzgleichheit zu erhalten Object
. Nächste Woche fügt der Bibliotheksbetreuer für Bar "hilfreich" eine Standardimplementierung hinzu equals
. Ups! Jetzt wurde die Semantik von Foo
durch eine Schnittstelle in einer anderen Wartungsdomäne "hilfreich" unterbrochen, indem ein Standard für eine allgemeine Methode hinzugefügt wurde.
Standardeinstellungen sollen Standardeinstellungen sein. Das Hinzufügen einer Standardeinstellung zu einer Schnittstelle, an der keine vorhanden war (irgendwo in der Hierarchie), sollte die Semantik konkreter Implementierungsklassen nicht beeinflussen. Aber wenn Standardeinstellungen Objektmethoden "überschreiben" könnten, wäre das nicht wahr.
Obwohl es als harmloses Feature erscheint, ist es in der Tat ziemlich schädlich: Es erhöht die Komplexität bei geringer inkrementeller Ausdruckskraft und macht es viel zu einfach, dass gut gemeinte, harmlos aussehende Änderungen an separat kompilierten Schnittstellen untergraben werden die beabsichtigte Semantik der Implementierung von Klassen.