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:

  • Fournir des implémentations par défaut réelles. Exemple: Iterator.remove()
  • Permettant l’évolution de l’API JDK. Exemple: Iterable.forEach()

Du sharepoint 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 substitutions “accidentelles” dans les classes d’implémentation:

 interface Sender { // Convenience method to send an empty message default final void send() { send(null); } // Implementations should only implement this method void send(Ssortingng 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(Ssortingng message); } 

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

À un moment donné, le support des modificateurs tels que static méthodes static et final sur les interfaces n’a pas encore été complètement exploré, citant Brian Goetz :

L’autre partie concerne la mesure dans laquelle nous allons prendre en charge les outils de création de classes dans les interfaces, telles 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 soaps pas encore

Depuis cette date fin 2011, de toute évidence, le support static méthodes static dans les interfaces a é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 ) n’a jamais réussi à se connecter aux interfaces Java 8?

    Cette question est, dans une certaine mesure, liée à la raison pour laquelle «synchronisé» n’est pas autorisé dans les méthodes d’interface Java 8.

    L’essentiel à comprendre sur les méthodes par défaut est que l’objective principal de la conception est l’ évolution de l’interface , et non pas «transformer les interfaces en traits (médiocres)». Bien qu’il y ait un certain chevauchement entre les deux, et que nous avons essayé de nous adapter à ce dernier, il est préférable de comprendre ces questions quand elles sont vues sous cet angle. (Notez également que les méthodes de classe seront différentes des méthodes d’interface, quelle que soit leur intention, car les méthodes d’interface peuvent être héritées de façon multiple.)

    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 d’interface, c’était un objective de conception critique que les méthodes par défaut puissent être ajoutées aux interfaces après coup de manière compatible avec les sources et les binarys.

    La réponse trop simple à “pourquoi pas les méthodes par défaut finales” est que le corps ne serait alors pas simplement l’implémentation par défaut, ce serait la seule implémentation. Bien que ce soit une réponse un peu trop simple, cela nous donne un indice 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 développeurs. Par exemple, en supposant que vous ayez:

     interface A { default void foo() { ... } } interface B { } class C implements A, B { } 

    Ici, tout est bon; C hérite de foo() de A En supposant maintenant que B soit modifié pour avoir une méthode foo , avec un défaut:

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

    Maintenant, quand nous allons recomstackr C , le compilateur nous dira qu’il ne sait pas quel comportement hériter pour foo() , donc C doit le remplacer (et pourrait choisir de déléguer à A.super.foo() si il a voulu garder le même comportement.) Mais que faire si B avait fait sa final par défaut, 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 comstackr sans surcharger foo() , mais il ne peut pas remplacer foo() s’il était final dans B

    Ceci n’est qu’un exemple, mais les méthodes finales sont en réalité un outil qui a plus de sens dans le monde des classes à un seul inheritance (généralement quel état de couple à un comportement) qu’à des interfaces qui ne font qu’append du comportement . Il est trop difficile de raisonner à propos de “quelles autres interfaces pourraient être mélangées dans l’implémentation finale”, et permettre à une méthode d’interface d’être finale causerait probablement ces problèmes (et ils ne seraient pas expliqués par la personne qui a écrit l’interface). mauvais utilisateur qui tente de le mettre en œuvre.)

    Une autre raison de les rejeter est qu’ils ne signifieraient pas ce que vous pensez qu’ils veulent dire. Une implémentation par défaut est uniquement prise en compte 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 super-classe implémentait déjà la méthode, la valeur par défaut serait ignorée, ce qui n’est probablement pas ce que l’auteur par défaut attendait en la déclarant finale. (Ce comportement d’inheritance reflète le centre de conception des méthodes par défaut – l’évolution des interfaces. Il devrait être possible d’append une méthode par défaut (ou une implémentation par défaut à une méthode d’interface existante) aux interfaces existantes qui ont déjà le comportement des classes existantes qui implémentent l’interface, garantissant que les classes qui ont déjà travaillé avant que les méthodes par défaut ne soient ajoutées, fonctionnera de la même manière en présence de méthodes par défaut.)

    Dans la liste de diffusion lambda, il y a beaucoup de discussions à ce sujet . Un de ceux qui semble contenir beaucoup de discussions à propos de tout cela est le suivant: Sur la visibilité de la méthode d’interface variée (était Final Defenders) .

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

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

    C’est une solution difficile à corriger sans append de nuances obscures ou de compatibilité à la langue. Une coupure de compatibilité de cette ampleur et de toute subtilité potentielle serait jugée inadmissible. Il faut donc trouver une solution qui ne brise pas le code existant.

    La réintroduction du mot-clé ‘package’ en tant que spécificateur d’access serait-elle viable? L’absence d’un spécificateur dans une interface impliquerait un access public et l’absence d’un spécificateur dans une classe implique un access par paquet. Les spécificateurs qui ont du sens dans une interface ne sont pas clairs – en particulier si, pour minimiser le fardeau de connaissance des développeurs, nous devons nous assurer que les spécificateurs d’access signifient la même chose à la fois en classe et en interface s’ils sont présents.

    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 sont pas si certain.

    Y a-t-il eu une communication claire sur la question de savoir s’il s’agit même d’une possible discussion de fond? Sinon, devrait-il être tenu ailleurs.

    La réponse de Brian Goetz fut finalement:

    Oui, cela est déjà exploré.

    Cependant, permettez-moi de définir des attentes réalistes – les fonctionnalités de langage / VM ont un long délai d’exécution, même les plus sortingviaux comme celui-ci. Le temps de proposer de nouvelles idées de fonctionnalités linguistiques pour Java SE 8 est à peu près passé.

    Donc, très probablement, cela n’a jamais été mis en œuvre car cela ne faisait jamais partie du champ d’application. Il n’a jamais été proposé à temps pour être considéré.

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

    Et vous avez exactement ce que vous souhaitiez. C’est exactement ce que cette fonctionnalité ajoute – inheritance multiple du comportement. Bien sûr, nous comprenons que les gens les utiliseront comme des traits. Et nous avons travaillé dur pour nous assurer que le modèle d’inheritance qu’ils offrent est simple et suffisamment clair pour que les gens puissent obtenir de bons résultats dans une grande variété de situations. En 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 «aw, vous n’avez pas été assez loin» dans certains cas. Mais en réalité, la plupart de ce fil semble grogner que le verre est rempli à 98%. Je prendrai ce 98% et continuerai!

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

    Il sera difficile de trouver et d’identifier “LA” réponse, pour les résonances mentionnées dans les commentaires de @EJP: Il y a environ 2 (+/- 2) personnes dans le monde qui peuvent donner une réponse définitive. Et dans le doute, la réponse pourrait être quelque chose comme “Soutenir les méthodes finales par défaut ne semble pas valoir la peine de restructurer les mécanismes internes de résolution des appels”. Il s’agit bien sûr de spéculations, mais au moins de preuves subtiles, comme cette déclaration (par l’une des deux personnes) dans la liste de diffusion OpenJDK :

    “Je suppose que si les méthodes” par défaut final “étaient autorisées, elles pourraient nécessiter une réécriture de l’invocation spéciale interne à une interface d’invocation visible par l’utilisateur.”

    et des faits sortingviaux comme une méthode n’est tout simplement pas considérée comme une méthode (vraiment) finale lorsqu’elle est une méthode default , telle qu’elle est actuellement implémentée dans la méthode Method :: is_final_method dans OpenJDK.

    En outre, il est difficile de trouver des informations “d’autorité”, même avec des recherches Web excessives et en lisant des journaux de validation. Je pensais que cela pourrait être lié à des ambiguïtés potentielles lors de la résolution d’appels de méthode d’interface avec l’instruction invokeinterface et les appels de méthode de classe, correspondant à l’instruction invokevirtual : Pour l’instruction invokevirtual , il peut y avoir une simple recherche vtable. soit être hérité d’une super-classe, soit implémenté directement par la classe. Contrairement à cela, un appel invokeinterface doit examiner le site d’appel respectif pour savoir à quelle interface cet appel fait référence (ceci est expliqué plus en détail dans la page InterfaceCalls du wiki HotSpot). Cependant, les méthodes final ne sont pas insérées dans la vtable , ou remplacent les entrées existantes de la vtable (voir klassVtable.cpp. Ligne 333 ), et les méthodes par défaut remplacent les entrées existantes dans la vtable (voir klassVtable.cpp, Ligne 202 ). Donc, la raison réelle (et donc la réponse) doit être cachée plus profondément dans les mécanismes de résolution d’appels (plutôt complexes), mais ces références seront peut-être néanmoins considérées comme utiles, ne serait-ce que pour les autres à partir de ce.

    Je ne pense pas qu’il soit nécessaire de spécifier de manière final une méthode d’interface de convienience, mais je peux convenir que cela peut être utile, mais apparemment, les coûts ont surpassé les avantages.

    Ce que vous êtes censé faire, de toute façon, consiste à écrire un javadoc approprié pour la méthode par défaut, en indiquant exactement ce que la méthode est autorisée ou non. 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 pourrait écrire une Collection qui adhère à l’interface et ensuite, faire des choses dans les méthodes absolument contre-intuitives, il n’y a aucun moyen de s’en protéger, si ce n’est d’écrire des tests unitaires complets.