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

335

L'une des fonctionnalités les plus utiles de Java 8 est la nouvelle default méthodes 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 d'un 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" dans l'implémentation des 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à une 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, defaultet finalsont évidemment des mots-clés contradictoires, mais le mot-clé par défaut lui-même n'aurait pas été strictement requis , donc je suppose que cette contradiction est délibérée, pour refléter les différences subtiles entre les "méthodes de classe avec le corps" (juste méthodes) et "interface" méthodes avec corps " (méthodes par défaut), c'est-à-dire des différences que je n'ai pas encore comprises.

À un certain moment, la prise en charge des modificateurs comme staticet finalsur les méthodes d'interface n'était pas encore complètement explorée, citant Brian Goetz :

L'autre partie est de savoir jusqu'où nous allons aller pour prendre en charge les outils de création de classe 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 savons pas encore

Depuis cette époque fin 2011, évidemment, le support des staticméthodes dans les interfaces a été ajouté. De toute évidence, cela a ajouté beaucoup de valeur aux bibliothèques JDK elles-mêmes, comme avec Comparator.comparing().

Question:

Quelle est la raison final(et aussi static final) qui n'est jamais arrivée aux interfaces Java 8?

Lukas Eder
la source
10
Je suis désolé d'être une couverture humide, mais la seule façon d'obtenir une réponse à la question exprimée dans le titre dans les termes de SO est via une citation de Brian Goetz ou du groupe d'experts JSR. Je comprends que BG a demandé un débat public, mais c'est exactement ce qui contrevient aux termes de l'OP, car il est «principalement basé sur l'opinion». Il me semble que les responsabilités sont esquivées ici. C'est le travail du groupe d'experts, et du processus de communauté Java au sens large, de stimuler la discussion et de trouver des justifications. Pas des SO. Je vote donc pour la clôture comme «principalement basée sur l'opinion».
Marquis de Lorne
2
Comme nous le savons tous, finalempêche une méthode d'être surchargée, et vu comment vous DEVEZ remplacer les méthodes héritées des interfaces, je ne vois pas pourquoi il serait logique de la rendre définitive. A moins que cela ne signifie que la méthode est définitive APRÈS l'avoir remplacée une fois .. Dans ce cas, peut-être y a-t-il des difficultés? Si je ne comprends pas ce droit, laissez-moi kmow. Semble intéressant
Vince Emigh
22
@EJP: "Si je peux le faire, vous aussi, et répondez à votre propre question, qui n'aurait donc pas dû être posée." Cela s'appliquerait à presque toutes les questions sur ce forum, non? Je pourrais toujours rechercher un sujet sur Google pendant 5 heures, puis apprendre quelque chose que tout le monde doit apprendre de la même manière. Ou nous attendons encore quelques minutes pour que quelqu'un donne une réponse encore meilleure que tout le monde à l'avenir (y compris les 12 votes positifs et les 8 étoiles jusqu'à présent) puisse en profiter, car SO est si bien référencé sur Google. Donc oui. Cette question peut parfaitement s'intégrer dans le formulaire de questions et réponses de SO.
Lukas Eder
5
@VinceEmigh " ... voir comment vous DEVEZ remplacer les méthodes héritées des interfaces ... " Ce n'est pas vrai dans Java 8. Java 8 vous permet d'implémenter des méthodes dans les interfaces, ce qui signifie que vous n'avez pas à les implémenter dans l'implémentation Des classes. Ici, finalaurait une utilité pour empêcher l'implémentation des classes de remplacer l'implémentation par défaut d'une méthode d'interface.
awksp
28
@EJP vous ne savez jamais: Brian Goetz peut répondre !
assylias

Réponses:

419

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 à comprendre au sujet des méthodes par défaut est que l'objectif principal de la conception est l' évolution de l'interface , et non "transformer les interfaces en traits (médiocres)". Bien qu'il y ait un certain chevauchement entre les deux, et nous avons essayé de nous adapter à ce dernier là où il ne gênait pas le premier, ces questions sont mieux comprises lorsqu'elles sont considérées sous cet angle. (Notez également que les méthodes de classe sont vont être différentes des méthodes d'interface, peu importe ce que l'intention, en vertu du fait que l' on peut multiplier les méthodes héritées de l' interface.)

L'idée de base d'une méthode par défaut est: c'est 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 parce que le centre de conception était l'évolution des interfaces, c'était un objectif de conception critique que les méthodes par défaut puissent être ajoutées aux interfaces après coup. une manière compatible avec les sources et compatible binaire.

La réponse trop simple à "pourquoi pas les méthodes par défaut finales" est qu'alors le corps ne serait plus 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 va 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; Chérite foo()de A. Supposons maintenant que l' Bon change pour avoir une foométhode, avec une valeur par défaut:

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

Maintenant, lorsque nous allons recompiler C, le compilateur nous dira qu'il ne sait pas quel comportement hériter foo(), il Cdoit donc le remplacer (et pourrait choisir de déléguer A.super.foo()s'il souhaite conserver le même comportement.) Mais que faire si Bavait fait son défaut final, et An'est pas sous le contrôle de l'auteur de C? Maintenant Cest irrémédiablement brisé; il ne peut pas compiler sans foo()remplacer, mais il ne peut pas remplacer foo()s'il était final enB .

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 associent l'état au comportement), qu'aux interfaces qui contribuent simplement au comportement et peuvent être multipliées hérité. Il est trop difficile de raisonner sur "quelles autres interfaces pourraient être mélangées dans l'implémentateur éventuel", et permettre à une méthode d'interface d'être finale causerait probablement ces problèmes (et ils exploseraient non pas sur la personne qui a écrit l'interface, mais sur le utilisateur pauvre qui essaie de l'implémenter.)

Une autre raison de les interdire est qu'ils ne signifieraient pas ce que vous pensez qu'ils signifient. Une implémentation par défaut n'est prise en compte que si la classe (ou ses superclasses) 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 avait déjà implémenté la méthode, la valeur par défaut serait ignorée, ce qui n'est probablement pas ce à quoi l'auteur par défaut s'attendait en la déclarant finale. (Ce comportement d'héritage reflète le centre de conception des méthodes par défaut - évolution de l'interface. 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 qui ont déjà des implémentations, sans modifier la comportement des classes existantes qui implémentent l'interface,

Brian Goetz
la source
88
Fantastique de vous voir répondre aux questions sur les nouvelles fonctionnalités du langage! Il est très utile d'obtenir des clarifications sur l'intention et les détails de la conception lors de la détermination de la façon dont nous devrions utiliser les nouvelles fonctionnalités. D'autres personnes impliquées dans la conception contribuent-elles au SO - ou faites-vous simplement cela vous-même? Je suivrai vos réponses sous la balise java-8 - J'aimerais savoir s'il y a d'autres personnes qui font de même pour que je puisse les suivre aussi.
Shorn
10
@Shorn Stuart Marks a été actif dans le tag java-8. Jeremy Manson a posté dans le passé. Je me souviens également d'avoir vu des messages de Joshua Bloch mais je ne les trouve pas pour le moment.
assylias
1
Félicitations pour avoir proposé des méthodes d'interface par défaut comme un moyen beaucoup plus élégant d'accomplir ce que fait C # avec ses méthodes d'extension mal conçues et plutôt laides. En ce qui concerne cette question, la réponse concernant l'impossibilité de résoudre un conflit de nom résout en quelque sorte le problème, mais les autres raisons invoquées étaient une philologie peu convaincante. (Si je veux qu'une méthode d'interface soit définitive, alors vous devez supposer que je dois avoir mes propres bonnes raisons de vouloir interdire à quiconque de fournir une implémentation différente de la mienne.)
Mike Nakis
1
Pourtant, la résolution des conflits de noms aurait pu être obtenue d'une manière similaire à la façon dont elle est effectuée en C #, en ajoutant le nom de l'interface particulière implémentée à la déclaration de la méthode d'implémentation. Donc, ce que je retiens de tout cela, c'est que les méthodes d'interface par défaut ne peuvent pas être définitives en Java car cela aurait nécessité des modifications supplémentaires de la syntaxe, ce qui ne vous plaisait pas. (Trop c-sharpish peut-être?)
Mike Nakis
18
@Trying Les classes abstraites sont toujours le seul moyen d'introduire l'état ou d'implémenter les principales méthodes Object. Les méthodes par défaut sont pour le comportement pur ; les classes abstraites sont pour le comportement couplé à l'état.
Brian Goetz
43

La liste de diffusion lambda contient de nombreuses discussions à ce sujet . L'un de ceux qui semble contenir beaucoup de discussions sur 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 d'origine pose quelque chose de très similaire à votre question:

La décision de rendre tous les membres de l'interface publics était en effet une décision malheureuse. Le fait que toute utilisation d'interface dans la conception interne expose des détails privés de mise en œuvre est important.

C'est difficile à corriger sans ajouter des nuances obscures ou de rupture de compatibilité à la langue. Une rupture de compatibilité de cette ampleur et de cette subtilité potentielle serait considérée comme inadmissible, donc une solution doit exister qui ne casse pas le code existant.

La réintroduction du mot clé «package» en tant que spécificateur d'accès pourrait être viable. Son absence d'un spécificateur dans une interface impliquerait un accès public et l'absence d'un spécificateur dans une classe implique un accès au package. Les spécificateurs qui ont du sens dans une interface ne sont pas clairs - surtout si, pour minimiser la charge de connaissances des développeurs, nous devons nous assurer que les spécificateurs d'accès signifient la même chose dans la classe et l'interface s'ils sont présents.

En l'absence de méthodes par défaut, j'aurais émis l'hypothèse que le spécificateur d'un membre dans une interface devait être au moins aussi visible que l'interface elle-même (afin que l'interface puisse en fait ê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 quant à savoir s'il s'agit même d'une discussion de portée possible? Sinon, devrait-il avoir lieu ailleurs.

Finalement, Brian Goetz a répondu :

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

Cependant, permettez-moi de définir des attentes réalistes - les fonctionnalités de langage / VM ont un long délai, même celles qui semblent triviales comme celle-ci. Le temps de proposer de nouvelles idées de fonctionnalités de langage pour Java SE 8 est quasiment passé.

Il est donc très probable qu'il n'ait jamais été mis en œuvre car il ne faisait jamais partie de la portée. Il n'a jamais été proposé à temps pour être examiné.

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

Et vous avez obtenu exactement ce que vous souhaitiez. C'est exactement ce que cette fonctionnalité ajoute: l'héritage multiple du comportement. Bien sûr, nous comprenons que les gens les utiliseront comme traits. Et nous avons travaillé dur pour nous assurer que le modèle d'héritage qu'ils proposent est suffisamment simple et propre pour que les gens puissent obtenir de bons résultats dans une grande variété de situations. Nous avons, en même temps, choisi de ne pas les pousser au-delà de la limite de ce qui fonctionne simplement et proprement, et cela conduit à des réactions «aw, tu n'es pas allé assez loin» dans certains cas. Mais vraiment, la plupart de ce fil semble grogner que le verre n'est qu'à 98% plein. Je vais prendre ces 98% et continuer!

Donc, cela renforce ma théorie selon laquelle cela ne faisait tout simplement pas partie de la portée ou de la conception. Ce qu'ils ont fait était de fournir suffisamment de fonctionnalités pour faire face aux problèmes de l'évolution des API.

Edwin Dalorzo
la source
4
Je vois que j'aurais dû inclure l'ancien nom, "méthodes de défenseur", dans mon odyssée googler ce matin. +1 pour déterrer cela.
Marco13
1
Belle fouille de faits historiques. Vos conclusions correspondent bien à la réponse officielle
Lukas Eder
Je ne vois pas pourquoi cela briserait la compatibilité en arrière. la finale n'était pas autorisée auparavant. ils autorisent désormais le privé, mais pas protégé. myeh ... private ne peut pas implémenter ... une interface étendant une autre interface peut implémenter des parties d'une interface parent, mais elle doit exposer la capacité de surcharge à d'autres ...
mmm
17

Il sera difficile de trouver et identifier « LA » réponse, pour les resons mentionnés dans les commentaires de @EJP: Il y a environ 2 (+/- 2) personnes dans le monde qui peut donner la réponse définitive du tout . Et dans le doute, la réponse pourrait simplement être quelque chose comme "La prise en charge des méthodes par défaut finales ne semble pas valoir la peine de restructurer les mécanismes internes de résolution des appels". C'est de la spéculation, bien sûr, mais elle est au moins étayée par des 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'interface invocable interne vers l'interface invoquée 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 lorsqu'elle est une defaultméthode, comme cela est actuellement implémenté dans la méthode Method :: is_final_method dans OpenJDK.

Il est en effet difficile de trouver des informations vraiment "autoritaires", même avec des recherches Web excessives et en lisant les journaux de validation. J'ai pensé que cela pourrait être lié à des ambiguïtés potentielles lors de la résolution des appels de méthode d'interface avec l' invokeinterfaceinstruction et et les appels de méthode de classe, correspondant à l' invokevirtualinstruction: Pour l' invokevirtualinstruction, il peut y avoir une simple recherche de table , car la méthode doit soit être héritée à partir d'une superclasse, ou implémenté directement par la classe. Contrairement à cela, un invokeinterfaceappel doit examiner le site d'appel respectif pour savoir à quelle interface cet appel se réfère réellement (cela est expliqué plus en détail dans les InterfaceCalls page du HotSpot Wiki). cependant,finalles méthodes ne sont pas du tout insérées dans la table virtuelle ou remplacent les entrées existantes dans la table virtuelle (voir klassVtable.cpp. Ligne 333 ), et de même, les méthodes par défaut remplacent les entrées existantes dans la table virtuelle (voir klassVtable.cpp, ligne 202 ) . La raison réelle (et donc la réponse) doit donc être cachée plus profondément dans les mécanismes (plutôt complexes) de résolution d'appels de la méthode, mais peut-être que ces références seront néanmoins considérées comme utiles, ne serait-ce que pour d'autres qui parviennent à dériver la réponse réelle à partir de ce.

Marco13
la source
Merci pour l'intuition intéressante. La pièce de John Rose est une trace intéressante. Cependant, je ne suis toujours pas d'accord avec @EJP. À titre de contre-exemple, consultez ma réponse à une question très intéressante et très similaire de Peter Lawrey . Il est possible de découvrir des faits historiques, et je suis toujours heureux de les trouver ici sur Stack Overflow (où d'autre?). Bien sûr, votre réponse est encore spéculative, et je ne suis pas convaincu à 100% que les détails de la mise en œuvre de la JVM seraient la dernière raison (jeu de mots voulu) pour que le JLS soit écrit d'une manière ou d'une autre ...
Lukas Eder
@LukasEder Bien sûr, ces types de questions sont intéressantes et à mon humble avis s'inscrivent dans le modèle de questions et réponses. Je pense qu'il y a deux points cruciaux qui ont provoqué la controverse ici: Le premier est que vous avez demandé "la raison". Dans de nombreux cas, cela peut ne pas être officiellement documenté. Par exemple, il n'y a aucune "raison" mentionnée dans le JLS pour laquelle il n'y a pas de ints non signé , mais voir stackoverflow.com/questions/430346 ... ...
Marco13
... ... La seconde est que vous n'avez demandé que des "citations autoritaires", ce qui réduit le nombre de personnes qui osent écrire une réponse de "quelques dizaines" à ... "approximativement zéro". En dehors de cela, je ne sais pas comment le développement de la JVM et l'écriture du JLS sont imbriqués, c'est-à-dire dans quelle mesure le développement influence ce qui est écrit dans le JLS, mais ... j'éviterai toute spéculation ici; -)
Marco13
1
Je reste toujours mon cas. Voir qui a répondu à mon autre question :-) Il sera désormais clair pour toujours avec une réponse faisant autorité, ici sur Stack Overflow pourquoi il a été décidé de ne pas prendre en charge synchronizedles defaultméthodes.
Lukas Eder
3
@LukasEder je vois, la même chose ici. Qui aurait pu s'attendre à ça? Les raisons sont plutôt convaincantes, en particulier pour cette finalquestion, et il est quelque peu humiliant que personne d'autre ne semble penser à des exemples similaires (ou, peut-être, certains ont pensé à ces exemples, mais ne se sont pas sentis suffisamment autoritaires pour répondre). Alors maintenant (désolé, je dois le faire:) le dernier mot est prononcé.
Marco13
4

Je ne pense pas qu'il soit nécessaire de spécifier finalune méthode d'interface de convenance, je suis d'accord pour dire que cela peut être utile, mais apparemment les coûts ont surpassé les avantages.

Ce que vous êtes censé faire, de toute façon, est d'écrire du javadoc approprié 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 pourrait écrire un Collectionqui adhère à l'interface et ensuite faire des choses dans les méthodes qui sont absolument contre-intuitives, il n'y a aucun moyen de s'en protéger, à part écrire des tests unitaires approfondis.

skiwi
la source
2
Les contrats Javadoc sont une solution de contournement valide à l' exemple concret que j'ai cité dans ma question, mais la question ne concerne vraiment pas le cas d'utilisation de la méthode de l'interface de commodité. La question porte sur une raison faisant autorité pour laquelle il finala été décidé de ne pas être autorisé sur les interfaceméthodes Java 8 . Un rapport coût / bénéfice insuffisant est un bon candidat, mais jusqu'à présent, c'est de la spéculation.
Lukas Eder
0

Nous ajoutons un defaultmot-clé à notre méthode à l'intérieur d'un interfacequand nous savons que la classe étendant la mise en œuvre interfacepeut ou non overridenotre implémentation. Mais que se passe-t-il si nous voulons ajouter une méthode que nous ne voulons pas qu'une classe d'implémentation écrase? Eh bien, deux options s'offraient à nous:

  1. Ajoutez une default finalméthode.
  2. Ajoutez une staticméthode.

Maintenant, Java dit que si nous en classimplémentons deux ou plus interfaces, ils ont une defaultméthode avec exactement le même nom de méthode et la même signature, c'est-à-dire qu'ils sont en double, alors nous devons fournir une implémentation de cette méthode dans notre classe. Maintenant, dans le cas de default finalméthodes, nous ne pouvons pas fournir une implémentation et nous sommes bloqués. Et c'est pourquoi le finalmot-clé n'est pas utilisé dans les interfaces.

Ashutosh
la source