C'est encore un autre de ces problèmes de conception de langage qui semble "évidemment une bonne idée" jusqu'à ce que vous commenciez à creuser et que vous vous rendiez compte que c'est en fait une mauvaise idée.
Ce courrier a beaucoup sur le sujet (et sur d'autres sujets aussi.) Il y avait plusieurs forces de conception qui ont convergé pour nous amener à la conception actuelle:
- Le désir de garder le modèle d'héritage simple;
- Le fait qu'une fois que vous regardez au-delà des exemples évidents (par exemple, devenir
AbstractList
une interface), vous vous rendez compte que l'héritage de equals / hashCode / toString est fortement lié à l'héritage et à l'état uniques, et les interfaces sont héritées de manière multiple et sans état;
- Qu'elle a potentiellement ouvert la porte à des comportements surprenants.
Vous avez déjà abordé l'objectif «garder les choses simples»; les règles d'héritage et de résolution des conflits sont conçues pour être très simples (les classes gagnent les interfaces, les interfaces dérivées l'emportent sur les super-interfaces, et tout autre conflit est résolu par la classe d'implémentation.) Bien sûr, ces règles pourraient être modifiées pour faire une exception, mais Je pense que vous constaterez lorsque vous commencez à tirer sur cette chaîne, que la complexité incrémentielle n'est pas aussi petite que vous pourriez le penser.
Bien sûr, il y a un certain degré d'avantages qui justifierait plus de complexité, mais dans ce cas, ce n'est pas là. Les méthodes dont nous parlons ici sont equals, hashCode et toString. Ces méthodes concernent toutes intrinsèquement l'état de l'objet, et c'est la classe qui possède l'état, et non l'interface, qui est la mieux placée pour déterminer ce que signifie l'égalité pour cette classe (d'autant plus que le contrat d'égalité est assez fort; voir Efficace Java pour des conséquences surprenantes); les écrivains d'interface sont trop éloignés.
Il est facile de sortir l' AbstractList
exemple; ce serait bien si nous pouvions nous débarrasser AbstractList
et mettre le comportement dans l' List
interface. Mais une fois que vous avez dépassé cet exemple évident, il n'y a pas beaucoup d'autres bons exemples à trouver. À la racine, AbstractList
est conçu pour l'héritage unique. Mais les interfaces doivent être conçues pour l'héritage multiple.
De plus, imaginez que vous écrivez cette classe:
class Foo implements com.libraryA.Bar, com.libraryB.Moo {
// Implementation of Foo, that does NOT override equals
}
L' Foo
auteur examine les supertypes, ne voit aucune implémentation d'égaux et conclut que pour obtenir l'égalité de référence, il lui suffit d'hériter des égaux Object
. Puis, la semaine prochaine, le responsable de la bibliothèque de Bar ajoute "utilement" une equals
implémentation par défaut . Oups! Maintenant, la sémantique de Foo
a été brisée par une interface dans un autre domaine de maintenance, ajoutant "utilement" une valeur par défaut pour une méthode commune.
Les valeurs par défaut sont censées être des valeurs par défaut. L'ajout d'une valeur par défaut à une interface où il n'y en avait pas (n'importe où dans la hiérarchie) ne devrait pas affecter la sémantique des classes d'implémentation concrètes. Mais si les valeurs par défaut pouvaient "remplacer" les méthodes Object, ce ne serait pas vrai.
Ainsi, bien que cela semble être une fonctionnalité inoffensive, elle est en fait assez nuisible: elle ajoute beaucoup de complexité pour peu d'expressivité incrémentale, et cela rend beaucoup trop facile pour des modifications bien intentionnées et inoffensives des interfaces compilées séparément de saper la sémantique prévue de l'implémentation des classes.