Java8: Pourquoi est-il interdit de définir une méthode par défaut pour une méthode de java.lang.Object

130

Les méthodes par défaut sont un nouvel outil intéressant dans notre boîte à outils Java. Cependant, j'ai essayé d'écrire une interface qui définit une defaultversion de la toStringméthode. Java me dit que cela est interdit, car les méthodes déclarées dans java.lang.Objectne peuvent pas être defaultéditées. pourquoi est-ce le cas?

Je sais qu'il existe la règle "la classe de base gagne toujours", donc par défaut (jeu de mots;), toute defaultimplémentation d'une Objectméthode serait écrasée par la méthode de Objecttoute façon. Cependant, je ne vois aucune raison pour laquelle il ne devrait pas y avoir d'exception pour les méthodes de Objectla spécification. Surtout toStringqu'il peut être très utile d'avoir une implémentation par défaut.

Alors, quelle est la raison pour laquelle les concepteurs Java ont décidé de ne pas autoriser les defaultméthodes remplaçant les méthodes Object?

gexicide
la source
1
Je me sens tellement bien dans ma peau maintenant, en votant pour cela 100 fois et donc un badge d'or. bonne question!
Eugene

Réponses:

186

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 AbstractListune 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' AbstractListexemple; ce serait bien si nous pouvions nous débarrasser AbstractListet mettre le comportement dans l' Listinterface. Mais une fois que vous avez dépassé cet exemple évident, il n'y a pas beaucoup d'autres bons exemples à trouver. À la racine, AbstractListest 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' Fooauteur 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 equalsimplémentation par défaut . Oups! Maintenant, la sémantique de Fooa é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.

Brian Goetz
la source
13
Je suis heureux que vous ayez pris le temps d'expliquer cela et j'apprécie tous les facteurs qui ont été pris en compte. Je conviens que ce serait dangereux pour hashCodeet equals, mais je pense que ce serait très utile pour toString. Par exemple, une Displayableinterface pourrait définir une String display()méthode, et cela économiserait une tonne de passe-partout pour pouvoir définir default String toString() { return display(); }dans Displayable, au lieu d'exiger que chacun Displayableimplémente toString()ou étende une DisplayableToStringclasse de base.
Brandon
8
@Brandon Vous avez raison de dire qu'autoriser l'héritage de toString () ne serait pas dangereux de la même manière que ce serait pour equals () et hashCode (). D'un autre côté, maintenant, la fonctionnalité serait encore plus irrégulière - et vous encourriez toujours la même complexité supplémentaire concernant les règles d'héritage, pour le bien de cette méthode ... il semble préférable de tracer la ligne proprement là où nous l'avons fait .
Brian Goetz
5
@gexicide Si toString()est basé uniquement sur les méthodes d'interface, vous pouvez simplement ajouter quelque chose comme default String toStringImpl()à l'interface, et remplacer toString()dans chaque sous-classe pour appeler l'implémentation de l'interface - un peu moche, mais fonctionne, et mieux que rien. :) Une autre façon de le faire est de créer quelque chose comme Objects.hash(), Arrays.deepEquals()et Arrays.deepToString(). +1 pour la réponse de @ BrianGoetz!
Siu Ching Pong -Asuka Kenji-
3
Le comportement par défaut du toString () d'un lambda est vraiment désagréable. Je sais que l'usine lambda est conçue pour être très simple et rapide, mais cracher un nom de classe dérivé n'est vraiment pas utile. Avoir un default toString()remplacement dans une interface fonctionnelle nous permettrait - au moins - de faire quelque chose comme cracher la signature de la fonction et la classe parente de l'implémenteur. Mieux encore, si nous pouvions appliquer des stratégies toString récursives, nous pourrions parcourir la fermeture pour obtenir une très bonne description du lambda, et ainsi améliorer considérablement la courbe d'apprentissage lambda.
Groostav
Toute modification apportée à toString dans n'importe quelle classe, sous-classe, membre d'instance peut avoir cet effet sur l'implémentation de classes ou d'utilisateurs d'une classe. De plus, toute modification de l'une des méthodes par défaut affecterait probablement toutes les classes d'implémentation. Alors, quelle est la particularité de toString, hashCode lorsqu'il s'agit de modifier le comportement d'une interface? Si une classe étend une autre classe, elle pourrait en changer aussi. Ou s'ils utilisent le modèle de délégation. Une personne utilisant les interfaces Java 8 devra le faire en effectuant une mise à niveau. Un avertissement / erreur qui peut être supprimé sur la sous-classe aurait pu être fourni.
mmm
30

Il est interdit de définir des méthodes par défaut dans les interfaces pour les méthodes de java.lang.Object, car les méthodes par défaut ne seraient jamais "accessibles".

Les méthodes d'interface par défaut peuvent être écrasées dans les classes implémentant l'interface et l'implémentation de classe de la méthode a une priorité plus élevée que l'implémentation d'interface, même si la méthode est implémentée dans une superclasse. Étant donné que toutes les classes héritent de java.lang.Object, les méthodes dans java.lang.Objectauraient la priorité sur la méthode par défaut dans l'interface et seraient appelées à la place.

Brian Goetz d'Oracle fournit quelques détails supplémentaires sur la décision de conception dans cet article de liste de diffusion .

Jarnbjo
la source
3

Je ne vois pas dans la tête des auteurs du langage Java, donc on ne peut que deviner. Mais je vois de nombreuses raisons et je suis tout à fait d'accord avec elles dans ce numéro.

La principale raison de l'introduction des méthodes par défaut est de pouvoir ajouter de nouvelles méthodes aux interfaces sans rompre la compatibilité descendante des anciennes implémentations. Les méthodes par défaut peuvent également être utilisées pour fournir des méthodes «pratiques» sans qu'il soit nécessaire de les définir dans chacune des classes d'implémentation.

Aucun de ces éléments ne s'applique à toString et aux autres méthodes d'Object. En termes simples, les méthodes par défaut ont été conçues pour fournir le comportement par défaut là où il n'y a pas d'autre définition. Ne pas fournir des implémentations qui "concurrenceront" d'autres implémentations existantes.

La règle «la classe de base gagne toujours» a aussi ses bonnes raisons. On suppose que les classes définissent des implémentations réelles , tandis que les interfaces définissent des implémentations par défaut , qui sont un peu plus faibles.

En outre, l'introduction de TOUTES exceptions aux règles générales entraîne une complexité inutile et soulève d'autres questions. L'objet est (plus ou moins) une classe comme une autre, alors pourquoi devrait-il avoir un comportement différent?

Dans l'ensemble, la solution que vous proposez apporterait probablement plus d'inconvénients que d'avantages.

Marwin
la source
Je n'ai pas remarqué le deuxième paragraphe de la réponse de gexicide en postant la mienne. Il contient un lien qui explique le problème plus en détail.
Marwin
1

Le raisonnement est très simple, c'est parce que Object est la classe de base de toutes les classes Java. Donc, même si la méthode Object est définie comme méthode par défaut dans certaines interfaces, elle sera inutile car la méthode Object sera toujours utilisée. C'est pourquoi, pour éviter toute confusion, nous ne pouvons pas avoir de méthodes par défaut qui remplacent les méthodes de classe Object.

Kumar Abhishek
la source
1

Pour donner une réponse très pédante, il est uniquement interdit de définir une defaultméthode pour une méthode publique à partir de java.lang.Object. Il existe 11 méthodes à considérer, qui peuvent être classées de trois façons pour répondre à cette question.

  1. Six des Objectméthodes ne peut pas avoir des defaultméthodes parce qu'ils sont finalet ne peuvent être écartés du tout: getClass(), notify(), notifyAll(), wait(), wait(long)et wait(long, int).
  2. Trois des Objectméthodes ne peut pas avoir des defaultméthodes pour les raisons indiquées ci - dessus par Brian Goetz: equals(Object), hashCode()et toString().
  3. Deux des Objectméthodes peuvent avoir des defaultméthodes, bien que la valeur de ces valeurs par défaut soit au mieux discutable: clone()et finalize().

    public class Main {
        public static void main(String... args) {
            new FOO().clone();
            new FOO().finalize();
        }
    
        interface ClonerFinalizer {
            default Object clone() {System.out.println("default clone"); return this;}
            default void finalize() {System.out.println("default finalize");}
        }
    
        static class FOO implements ClonerFinalizer {
            @Override
            public Object clone() {
                return ClonerFinalizer.super.clone();
            }
            @Override
            public void finalize() {
                ClonerFinalizer.super.finalize();
            }
        }
    }
jaco0646
la source
.À quoi ça sert? Vous n'avez toujours pas répondu à la partie POURQUOI - "Alors, quelle est la raison pour laquelle les concepteurs Java ont décidé de ne pas autoriser les méthodes par défaut remplaçant les méthodes d'Object?"
pro_cheats