web-dev-qa-db-fra.com

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

Les méthodes par défaut sont un nouvel outil sympa dans notre Java boîte à outils). Cependant, j'ai essayé d'écrire une interface qui définit une version default de la méthode toString. = Java me dit que c'est interdit, car les méthodes déclarées dans Java.lang.Object ne peut pas être defaulted. pourquoi est-ce le cas?

Je sais qu'il y a la règle "la classe de base gagne toujours", donc par défaut (pun;), toute implémentation de default d'une méthode Object serait écrasée par la méthode de Object en tous cas. 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. Surtout pour toString il pourrait être très utile d'avoir une implémentation par défaut.

Alors, quelle est la raison pour laquelle Java) ont décidé de ne pas autoriser les méthodes default redéfinissant les méthodes de Object?

117
gexicide

C’est là un autre problème 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 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'après avoir passé en revue les exemples évidents (par exemple, transformer AbstractList en une interface), vous vous rendez compte que l'héritage d'égal à/hashCode/toString est fortement lié à un héritage et à un état uniques, et que les interfaces sont héritées de multiples et sans état. ;
  • Cela a potentiellement ouvert la porte à des comportements surprenants.

Vous avez déjà évoqué l'objectif "Keep it simple"; les règles d'héritage et de résolution de conflit 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 peuvent être modifiées pour créer une exception, mais Je pense que vous constaterez, lorsque vous commencerez à 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 avantage qui justifierait davantage de complexité, mais dans ce cas, ce n'est pas là. Les méthodes dont nous parlons sont égales, 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 le mieux placé pour déterminer la signification de l’égalité pour cette classe (d’autant plus que le contrat d’égalité est très fort; voir la section Efficacité). Java pour des conséquences surprenantes); les auteurs d’interface sont tout simplement trop éloignés.

Il est facile d'extraire l'exemple AbstractList; ce serait bien si nous pouvions nous débarrasser de AbstractList et mettre le comportement dans l'interface List. Mais une fois que vous avez dépassé cet exemple évident, vous ne trouverez plus beaucoup de bons exemples. À la racine, AbstractList est conçu pour un héritage unique. Mais les interfaces doivent être conçues pour un 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 écrivain examine les supertypes, ne voit aucune implémentation d’égal à égal, et conclut que pour obtenir l’égalité de référence, il lui suffit d’hériter d’égal à Object. Puis, la semaine prochaine, le responsable de la bibliothèque de Bar ajoutera "utilement" une implémentation par défaut de equals. Ooops! Maintenant, la sémantique de Foo a été cassée par une interface dans un autre domaine de maintenance "utilement" en ajoutant une valeur par défaut pour une méthode commune.

Les valeurs par défaut sont supposées être des valeurs par défaut. Ajouter une valeur par défaut à une interface où il n'y en avait aucune (n'importe où dans la hiérarchie) ne devrait pas affecter la sémantique des classes implémentées concrètement. Mais si les valeurs par défaut pouvaient "écraser" les méthodes Object, ce ne serait pas vrai.

Ainsi, bien que cela paraisse comme une fonctionnalité inoffensive, il est en fait très dommageable: cela ajoute beaucoup de complexité à une expressivité incrémentale réduite, et il est beaucoup trop facile pour des modifications bien intentionnées et inoffensives d’interfaces compilées séparément de saper la sémantique prévue pour l'implémentation de classes.

171
Brian Goetz

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

Les méthodes d'interface par défaut peuvent être remplacées dans les classes implémentant l'interface et l'implémentation de classe de la méthode a une priorité supérieure à celle 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 préséance sur la méthode par défaut de l'interface et serait invoqué à la place.

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

29
jarnbjo

Je ne vois pas dans la tête des Java, nous pouvons donc le deviner. Mais je vois de nombreuses raisons et suis tout à fait d’accord avec elles dans ce numéro.

La principale raison d'introduire des méthodes par défaut est de pouvoir ajouter de nouvelles méthodes aux interfaces sans rompre la compatibilité avec les versions antérieures des implémentations plus anciennes. 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.

Aucune de celles-ci ne s'applique à toString et aux autres méthodes de Object. En termes simples, les méthodes par défaut ont été conçues pour fournir le comportement par défaut en l’absence de toute 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 également de solides raisons. Il est supposé 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 les 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 aurait probablement plus d'inconvénients que d'avantages.

3
Marwin

Le raisonnement est très simple, car Object est la classe de base pour toutes les Java). Ainsi, même si la méthode de Object est définie comme méthode par défaut dans certaines interfaces, elle sera inutilisable, car Object Pour éviter toute confusion, nous ne pouvons pas avoir de méthodes par défaut qui surchargent les méthodes de la classe Object.

1
Kumar Abhishek

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

  1. Six des méthodes Object ne peuvent pas avoir de méthodes default car elles sont final et ne peuvent pas être remplacées du tout: getClass(), notify() , notifyAll(), wait(), wait(long) et wait(long, int).
  2. Trois des méthodes Object ne peuvent pas avoir de méthodes default pour les raisons données ci-dessus par Brian Goetz: equals(Object), hashCode() et toString().
  3. Deux des méthodes Object peuvent avoir des méthodes default, 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();
            }
        }
    }
    
1
jaco0646