web-dev-qa-db-fra.com

Pourquoi "final" n'est-il pas autorisé dans les méthodes d'interface Java 8?

L'une des fonctionnalités les plus utiles de Java 8 sont les nouvelles méthodes default sur les interfaces. Il y a essentiellement deux raisons (il peut y en avoir d'autres) pour lesquelles elles ont été introduites:

Du point de vue du concepteur d’API, j’aurais aimé pouvoir utiliser d’autres modificateurs sur les méthodes d’interface, par exemple. final. Cela serait utile lors de l'ajout de méthodes pratiques, empêchant les remplacements "accidentels" lors de l'implémentation de classes:

interface Sender {

    // Convenience method to send an empty message
    default final void send() {
        send(null);
    }

    // Implementations should only implement this method
    void send(String message);
}

Ce qui précède est déjà pratique courante si Sender était une classe:

abstract class Sender {

    // Convenience method to send an empty message
    final void send() {
        send(null);
    }

    // Implementations should only implement this method
    abstract void send(String message);
}

Maintenant, default et final sont évidemment des mots-clés contradictoires, mais le mot-clé par défaut lui-même n'aurait pas été strictement requis , je suppose donc que cette contradiction est délibérée, pour refléter les différences subtiles entre "méthodes de classe avec corps" (méthodes seulement) et "méthodes d’interface avec corps" (méthodes par défaut), c'est-à-dire des différences que je n'ai pas encore comprises.

À un moment donné, la prise en charge de modificateurs tels que static et final sur les méthodes d'interface n'était pas encore complètement explorée, citant Brian Goetz :

L’autre partie concerne la mesure dans laquelle nous allons aller pour prendre en charge les outils de création de classes dans les interfaces, tels que les méthodes finales, les méthodes privées, les méthodes protégées, les méthodes statiques, etc. La réponse est: nous ne le savons pas encore.

Depuis cette date, fin 2011, le support des méthodes static dans les interfaces a bien sûr été ajouté. Clairement, cela a ajouté beaucoup de valeur aux bibliothèques JDK elles-mêmes, comme avec Comparator.comparing() .

Question:

Quelle est la raison pour laquelle final (et aussi static final) ne parvient jamais à accéder à Java 8 interfaces?

317
Lukas Eder

Cette question est, dans une certaine mesure, liée à Quelle est la raison pour laquelle "synchronisé" n'est pas autorisé dans les méthodes d'interface Java 8?

L’essentiel pour comprendre les méthodes par défaut est que l’objectif principal de la conception est l’évolution des interfaces et non "de transformer les interfaces en traits (médiocres)". Bien qu'il y ait un certain chevauchement entre les deux et que nous ayons essayé de nous accommoder de ce dernier, cela ne nous a pas empêché de comprendre, ces questions sont mieux comprises lorsqu'elles sont envisagées sous cet angle. (Notez également que les méthodes de classe sont différentes des méthodes d'interface, quelle que soit l'intention, du fait que les méthodes d'interface peuvent être multipliées hérité.)

L'idée de base d'une méthode par défaut est la suivante: il s'agit d'une méthode d'interface avec une implémentation par défaut, et une classe dérivée peut fournir une implémentation plus spécifique. Et comme le centre de conception était une évolution de l’interface, il était primordial que les méthodes par défaut puissent être ajoutées aux interfaces après le fait dans une source- manière compatible et compatible binaire.

La réponse trop simple à "pourquoi pas aux méthodes par défaut finales" est qu'alors le corps ne serait alors pas simplement l'implémentation par défaut, mais bien la seule implémentation. Bien que ce soit une réponse un peu trop simple, cela nous donne une idée du fait que la question se dirige déjà dans une direction discutable.

Une autre raison pour laquelle les méthodes d'interface finales sont discutables est qu'elles créent des problèmes impossibles pour les implémenteurs. Par exemple, supposons que vous ayez:

interface A { 
    default void foo() { ... }
}

interface B { 
}

class C implements A, B { 
}

Ici, tout va bien; C hérite de foo() de A. Supposons maintenant que B soit modifié pour avoir une méthode foo, avec une valeur par défaut:

interface B { 
    default void foo() { ... }
}

Maintenant, quand nous allons recompiler C, le compilateur nous dira qu’il ne sait pas quel comportement hériter pour foo(), de sorte que C doit le remplacer (et peut choisir de déléguer à A.super.foo() s'il souhaite conserver le même comportement.) Mais que se passe-t-il si B avait rendu sa valeur par défaut final, et A n'est pas sous le contrôle de l'auteur de C? Maintenant, C est irrémédiablement brisé; il ne peut pas compiler sans remplacer foo(), mais il ne peut pas remplacer foo() s'il était final dans B.

Ce n'est qu'un exemple, mais le fait est que la finalité des méthodes est vraiment un outil qui a plus de sens dans le monde des classes à héritage unique (généralement qui associe un état à un comportement), plutôt que des interfaces qui contribuent simplement au comportement et peuvent être multipliées. hérité. Il est trop difficile de dire "quelles autres interfaces pourraient être mélangées dans l'implémenteur éventuel", et permettre à une méthode d'interface d'être définitive causerait vraisemblablement ces problèmes (et ils n'exploseraient pas sur la personne qui a écrit l'interface, mais sur la utilisateur pauvre qui essaie de le mettre en œuvre)

Une autre raison de les interdire est qu’ils ne veulent pas dire ce que vous pensez qu’ils veulent dire. Une implémentation par défaut n'est prise en compte que si la classe (ou ses super-classes) ne fournit pas de déclaration (concrète ou abstraite) de la méthode. Si une méthode par défaut était finale, mais qu'une superclasse l'implémentait déjà, la méthode par défaut serait ignorée, ce qui n'est probablement pas ce à quoi l'auteur par défaut s'attendait lors de la déclaration de finale. (Ce comportement d'héritage est le reflet du centre de conception des méthodes par défaut - évolution des interfaces. Il devrait être possible d'ajouter une méthode par défaut (ou une implémentation par défaut à une méthode d'interface existante) aux interfaces existantes déjà implémentées, sans modifier le paramètre. comportement des classes existantes qui implémentent l'interface, garantissant que les classes qui fonctionnaient déjà avant l'ajout des méthodes par défaut fonctionnent de la même manière en présence de méthodes par défaut.)

391
Brian Goetz

Dans la liste de diffusion lambda il y a beaucoup de discussions à ce sujet . L'un de ceux qui semble contenir beaucoup de discussions à propos de tout ça est le suivant: On Visibilité de la méthode d'interface variée (était Final defenders) .

Dans cette discussion, Talden, l'auteur de la question originale demande quelque chose de très similaire à votre question:

La décision de rendre publics tous les membres de l'interface était une décision malheureuse. Que toute utilisation de l'interface dans la conception interne expose les détails privés de l'implémentation est un gros problème.

C'est un problème difficile à résoudre sans ajouter quelques nuances obscures ou de compatibilité au langage. Une rupture de compatibilité de cette ampleur et une subtilité potentielle seraient considérées comme inacceptables. Il faut donc une solution qui ne rompt pas le code existant.

La réintroduction du mot clé 'package' en tant que spécificateur d'accès serait-elle viable? Son absence de spécificateur dans une interface impliquerait un accès public et l'absence de spécificateur dans une classe impliquerait un accès à un package. Il est difficile de savoir quels spécificateurs ont du sens dans une interface - en particulier si, pour minimiser le fardeau lié aux connaissances des développeurs, nous devons nous assurer que les spécificateurs d'accès ont le même sens en termes de classe et d'interface, le cas échéant.

En l'absence de méthodes par défaut, j'aurais spéculé que le spécificateur d'un membre dans une interface doit être au moins aussi visible que l'interface elle-même (l'interface peut donc être implémentée dans tous les contextes visibles) - avec des méthodes par défaut qui ne le sont pas. si certain.

Y a-t-il eu une communication claire sur le point de savoir s'il s'agit même d'une discussion de fond possible? Si non, devrait-il être tenu ailleurs.

Finalement réponse de Brian Goetz était:

Oui, cela est déjà à l'étude.

Toutefois, permettez-moi de définir des attentes réalistes: les fonctionnalités de language/VM ont un long délai, même les plus triviales. Le temps pour proposer de nouvelles idées de fonctionnalités de langage pour Java SE 8 est à peu près écoulé.

Donc, il est fort probable que cela n’a jamais été mis en œuvre car cela n’a jamais fait partie de la portée. Il n'a jamais été proposé à temps pour être considéré.

Dans une autre discussion animée sur les méthodes du défenseur final sur le sujet, Brian dit à nouvea :

Et vous avez exactement ce que vous souhaitiez. C'est exactement ce que cette fonctionnalité ajoute: un héritage multiple de comportement. Bien sûr, nous comprenons que les gens les utiliseront comme traits. Et nous avons travaillé d'arrache-pied pour faire en sorte que le modèle d'héritage qu'ils proposent soit simple et suffisamment propre pour que les gens puissent obtenir de bons résultats dans une grande variété de situations. Dans le même temps, nous avons choisi de ne pas les pousser au-delà des limites de ce qui fonctionne simplement et proprement, ce qui conduit à des réactions "ah, tu n'es pas allé assez loin" dans certains cas. Mais en réalité, la majeure partie de ce fil semble se plaindre de ce que le verre est rempli à 98%. Je vais prendre ce 98% et aller de l'avant!

Cela renforce donc ma théorie selon laquelle cela ne faisait tout simplement pas partie de la portée ou de la conception de leur projet. Ce qu'ils ont fait était de fournir suffisamment de fonctionnalités pour traiter les problèmes d'évolution des API.

42
Edwin Dalorzo

Il sera difficile de trouver et d’identifier "LA" réponse, comme indiqué dans les commentaires de @EJP: Il y a environ 2 (+/- 2) personnes dans le monde qui peuvent donner la réponse définitive du tout. Et dans le doute, la réponse pourrait bien être quelque chose comme "Soutenir les méthodes par défaut finales ne semblait pas valoir l'effort de restructurer les mécanismes internes de résolution des appels". Ceci est une spéculation, bien sûr, mais elle est au moins étayée par des preuves subtiles, comme ceci Déclaration (par l'une des deux personnes) dans la liste de diffusion OpenJDK :

"Je suppose que si les méthodes" final default "étaient autorisées, elles pourraient nécessiter une réécriture d'invokespecial interne vers une invokeinterface visible par l'utilisateur."

et des faits triviaux comme le fait qu'une méthode n'est tout simplement pas considérée comme une méthode (vraiment) finale quand il s'agit d'une méthode default telle qu'elle est actuellement implémentée dans le Method :: is_final_method dans OpenJDK.

Il est en effet difficile de trouver des informations véritablement "autoritaires", même avec des recherches Web excessives et en lisant des journaux de validation. Je pensais que cela pouvait être lié à des ambiguïtés potentielles lors de la résolution d'appels de méthode d'interface avec l'instruction invokeinterface et et d'appels de méthode de classe, correspondant à l'instruction invokevirtual: pour l'instruction invokevirtual, il peut y avoir une simple recherche vtable , car la méthode doit être héritée d'une superclasse ou implémentée directement par la classe. Contrairement à cela, un appel invokeinterface doit examiner le site d’appel respectif pour déterminer à quelle interface cet appel fait réellement référence (cela est expliqué plus en détail dans le paragraphe - InterfaceCalls page du HotSpot Wiki). Cependant, les méthodes final ne sont pas insérées dans la vtable , ou remplacent les entrées existantes dans la vtable (voir klassVtable.cpp. Line 3 ), et de la même manière, les méthodes par défaut remplacent les entrées existantes dans la vtable (voir klassVtable.cpp, ligne 202 ). Ainsi, la raison actuelle (et donc la réponse) doit être cachée plus profondément dans les mécanismes de résolution d'appels de méthodes (plutôt complexes), mais peut-être que ces références seront néanmoins considérées comme utiles, que ce soit uniquement pour les autres qui parviennent à en tirer la réponse réelle.

16
Marco13

Je ne pense pas qu'il soit nécessaire de spécifier final sur une méthode d'interface de convienience. Je suis cependant d'accord sur le fait que cela peut être utile, mais apparemment, les coûts dépassent les avantages.

Ce que vous êtes censé faire, de toute façon, consiste à écrire le bon javadoc pour la méthode par défaut, en montrant exactement ce que la méthode est et n’est pas autorisée à faire. De cette façon, les classes implémentant l'interface "ne sont pas autorisées" à modifier l'implémentation, bien qu'il n'y ait aucune garantie.

N'importe qui peut écrire une Collection qui adhère à l'interface, puis applique des méthodes absolument contre-intuitives. Il n'y a aucun moyen de vous en protéger, mis à part l'écriture de tests unitaires approfondis.

4
skiwi