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

StackOverflow https://stackoverflow.com//questions/24016962

Question

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 un default version du toString méthode.Java me dit que c'est interdit, puisque les méthodes déclarées dans java.lang.Object n'est peut être pas defaultéd.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 ;), n'importe quel default mise en œuvre d'un Object la méthode serait écrasée par la méthode de Object de toute façon.Cependant, je ne vois aucune raison pour laquelle il ne devrait pas y avoir d'exception pour les méthodes de Object dans la spécification.En particulier pour toString il pourrait ê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 default méthodes remplaçant les méthodes de Object?

Était-ce utile?

La solution

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 réalisiez que c'est en fait une mauvaise idée.

Ce courrier a beaucoup de choses sur le sujet (et sur d'autres sujets aussi.) Plusieurs forces de conception 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 l'on dépasse les exemples évidents (par exemple, tourner AbstractList dans une interface), vous réalisez que l'héritage de equals/hashCode/toString est fortement lié à un héritage et à un état uniques, et que les interfaces sont héritées de plusieurs fois et sans état ;
  • Que cela a potentiellement ouvert la porte à des comportements surprenants.

Vous avez déjà évoqué 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 l'emportent sur les interfaces, les interfaces dérivées l'emportent sur les superinterfaces 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 commencerez à tirer sur cette corde, que la complexité incrémentielle n'est pas aussi petite qu'on pourrait le penser.

Bien sûr, il existe un certain degré d’avantage qui justifierait une plus grande complexité, mais dans ce cas-ci, ce n’est pas le cas.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 l'égalité signifie pour cette classe (d'autant plus que le contrat d'égalité est assez fort ;voir Effective Java pour quelques conséquences surprenantes) ;les rédacteurs d’interface sont tout simplement trop éloignés.

Il est facile de retirer le AbstractList exemple;ce serait bien si nous pouvions nous en débarrasser AbstractList et mettre le comportement dans le List interface.Mais une fois que l’on dépasse cet exemple évident, il n’existe pas beaucoup d’autres bons exemples.A la racine, AbstractList est conçu pour un héritage unique.Mais les interfaces doivent être conçues pour l’héritage multiple.

De plus, imaginez que vous écrivez ce cours :

class Foo implements com.libraryA.Bar, com.libraryB.Moo { 
    // Implementation of Foo, that does NOT override equals
}

Le Foo L'auteur examine les supertypes, ne voit aucune implémentation d'égal et conclut que pour obtenir l'égalité de référence, il lui suffit d'hériter des égaux de Object.Puis, la semaine prochaine, le responsable de la bibliothèque de Bar ajoute "utilement" un fichier par défaut equals mise en œuvre.Oups !Maintenant, la sémantique de Foo ont été cassés par une interface dans un autre domaine de maintenance en ajoutant "utilement" une valeur par défaut pour une méthode commune.

Les valeurs par défaut sont censées être les valeurs par défaut.L'ajout d'une valeur par défaut à une interface là 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, même si cela semble être une fonctionnalité inoffensive, elle est en fait assez dangereuse :cela ajoute beaucoup de complexité pour peu d'expressivité incrémentielle, et cela rend beaucoup trop facile les modifications bien intentionnées et apparemment inoffensives apportées aux interfaces compilées séparément pour saper la sémantique prévue de l'implémentation des classes.

Autres conseils

Il est interdit de définir des méthodes par défaut dans les interfaces pour les méthodes de java.lang.Object, puisque 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 de l'interface, même si la méthode est implémentée dans une superclasse.Puisque toutes les classes héritent de java.lang.Object, les méthodes dans java.lang.Object aurait priorité sur la méthode par défaut dans l'interface et serait invoquée à la place.

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

Je ne vois pas dans la tête des auteurs du langage Java, nous ne pouvons donc que deviner.Mais je vois de nombreuses raisons et je suis absolument d’accord avec elles sur cette question.

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é ascendante 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 défaut comportement pour lequel il n’existe pas d’autre définition.Ne pas fournir d'implémentations qui « rivaliseront » avec d'autres implémentations existantes.

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

De plus, 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 les autres, 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.

Le raisonnement est très simple, c'est parce que Object est la classe de base de toutes les classes Java.Ainsi, même si la méthode de l'objet est définie comme méthode par défaut dans une interface, elle sera inutile car la méthode de l'objet 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 la classe Object.

Pour donner une réponse très pédante, il est seulement interdit de définir un default méthode pour un publique méthode de java.lang.Object.Il existe 11 méthodes à considérer, qui peuvent être classées en trois manières pour répondre à cette question.

  1. Six des Object les méthodes ne peuvent pas avoir default méthodes parce qu'elles sont final et ne peut pas être annulé du tout : getClass(), notify(), notifyAll(), wait(), wait(long), et wait(long, int).
  2. Trois des Object les méthodes ne peuvent pas avoir default méthodes pour les raisons évoquées ci-dessus par Brian Goetz : equals(Object), hashCode(), et toString().
  3. Deux des Object méthodes peut avoir default mé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();
            }
        }
    }
    
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top