En Java 8, les interfaces peuvent contenir des méthodes implémentées, des méthodes statiques et les méthodes dites "par défaut" (que les classes d'implémentation n'ont pas besoin de remplacer).
À mon avis (probablement naïf), il n’était pas nécessaire de violer des interfaces comme celle-ci. Les interfaces ont toujours été un contrat que vous devez remplir, et c'est un concept très simple et pur. Maintenant, c'est un mélange de plusieurs choses. À mon avis:
- les méthodes statiques n'appartiennent pas aux interfaces. Ils appartiennent à des classes d'utilitaires.
- Les méthodes "par défaut" n'auraient absolument pas dû être autorisées dans les interfaces. Vous pouvez toujours utiliser une classe abstraite à cette fin.
En bref:
Avant Java 8:
- Vous pouvez utiliser des classes abstraites et régulières pour fournir des méthodes statiques et par défaut. Le rôle des interfaces est clair.
- Toutes les méthodes d'une interface doivent être remplacées par l'implémentation de classes.
- Vous ne pouvez pas ajouter une nouvelle méthode dans une interface sans modifier toutes les implémentations, mais c'est en fait une bonne chose.
Après Java 8:
- Il n'y a pratiquement aucune différence entre une interface et une classe abstraite (hormis l'héritage multiple). En fait, vous pouvez émuler une classe normale avec une interface.
- Lors de la programmation des implémentations, les programmeurs peuvent oublier de remplacer les méthodes par défaut.
- Il y a une erreur de compilation si une classe tente d'implémenter deux interfaces ou plus ayant une méthode par défaut avec la même signature.
- En ajoutant une méthode par défaut à une interface, chaque classe d'implémentation hérite automatiquement de ce comportement. Certaines de ces classes n'ont peut-être pas été conçues avec cette nouvelle fonctionnalité, ce qui peut poser problème. Par exemple, si quelqu'un ajoute une nouvelle méthode par défaut
default void foo()
à une interfaceIx
, la classeCx
implémentantIx
et disposant d'unefoo
méthode privée avec la même signature ne compile pas.
Quelles sont les principales raisons de ces changements majeurs et quels sont les nouveaux avantages (le cas échéant)?
java
programming-languages
interfaces
java8
Monsieur Smith
la source
la source
@Deprecated
catégorie! Les méthodes statiques sont l'une des constructions les plus maltraitées de Java, à cause de l'ignorance et de la paresse. De nombreuses méthodes statiques impliquent généralement des programmeurs incompétents, augmentent le couplage de plusieurs ordres de grandeur et sont un cauchemar pour les tests unitaires et les refactorisations lorsque vous réalisez pourquoi elles sont une mauvaise idée!Réponses:
Un bon exemple de motivation pour les méthodes par défaut est la bibliothèque standard Java, où vous avez maintenant
au lieu de
Je ne pense pas qu'ils auraient pu le faire autrement sans plus d'une implémentation identique de
List.sort
.la source
IEnumerable<Byte>.Append
pour les joindre, puis appelezCount
-moi, puis dites-moi comment les méthodes d'extension résolvent le problème. SiCountIsKnown
etCount
étaient membres deIEnumerable<T>
, le retour deAppend
pourrait annoncerCountIsKnown
si les collections constituantes le faisaient, mais sans de telles méthodes, cela n’est pas possible.La réponse correcte se trouve en fait dans la documentation Java , qui dit:
Cela a longtemps été une source de douleur pour Java, car les interfaces avaient tendance à être impossibles à évoluer une fois rendues publiques. (Le contenu de la documentation est lié au document auquel vous avez lié un commentaire: Evolution de l'interface via des méthodes d'extension virtuelles .) En outre, l'adoption rapide de nouvelles fonctionnalités (par exemple, lambdas et les nouvelles API de flux) ne peut être réalisée qu'en étendant la interfaces de collections existantes et fournissant des implémentations par défaut. Briser la compatibilité binaire ou introduire de nouvelles API signifierait que plusieurs années s'écouleraient avant que les fonctionnalités les plus importantes de Java 8 ne soient couramment utilisées.
La raison pour laquelle les méthodes statiques ont été autorisées dans les interfaces est à nouveau révélée par la documentation: cela vous facilite la tâche d’organiser des méthodes auxiliaires dans vos bibliothèques; vous pouvez conserver des méthodes statiques spécifiques à une interface dans la même interface plutôt que dans une classe séparée. En d'autres termes, les classes d'utilitaires statiques comme
java.util.Collections
peuvent maintenant (enfin) être considérées comme un anti-motif, en général (bien sûr, pas toujours ). Je suppose que l'ajout de la prise en charge de ce comportement était trivial une fois les méthodes d'extension virtuelle mises en œuvre, sinon cela n'aurait probablement pas été fait.Sur une note similaire, un exemple de la façon dont ces nouvelles fonctionnalités peuvent être utiles est de considérer une classe qui m'a récemment ennuyé,
java.util.UUID
. Il ne fournit pas vraiment de support pour les types UUID 1, 2 ou 5 et ne peut pas être facilement modifié pour le faire. Il est également bloqué par un générateur aléatoire prédéfini qui ne peut pas être remplacé. L'implémentation de code pour les types d'UUID non pris en charge nécessite soit une dépendance directe à une API tierce plutôt qu'une interface, soit la maintenance du code de conversion et le coût du ramassage des déchets supplémentaire qui va avec. Avec les méthodes statiques,UUID
aurait pu plutôt être défini comme une interface, permettant de véritables implémentations tierces des pièces manquantes. (SiUUID
étaient définis à l'origine comme une interface, nous aurions probablement une sorte de maladroitUuidUtil
classe avec des méthodes statiques, ce qui serait affreux aussi.) De nombreuses API de base de Java sont dégradées en ne se basant pas sur des interfaces, mais à partir de Java 8, le nombre d'excuses pour ce mauvais comportement a heureusement diminué.Il n'est pas correct de dire qu'il n'y a pratiquement aucune différence entre une interface et une classe abstraite , car les classes abstraites peuvent avoir un état (c'est-à-dire, déclarer des champs), contrairement aux interfaces. Il n’est donc pas équivalent à un héritage multiple ni même à un héritage de type mixin. Les mixins appropriés (tels que les traits de Groovy 2.3 ) ont accès à state. (Groovy prend également en charge les méthodes d'extension statique.)
Ce n’est pas non plus une bonne idée de suivre l’exemple de Doval , à mon avis. Une interface est censée définir un contrat, mais elle n'est pas censée faire respecter le contrat. (Pas de toute façon en Java.) La vérification d’une implémentation relève de la responsabilité d’une suite de tests ou d’un autre outil. La définition des contrats pourrait se faire avec des annotations, et OVal est un bon exemple, mais je ne sais pas s'il prend en charge les contraintes définies sur les interfaces. Un tel système est réalisable, même s'il n'en existe pas actuellement. (Les stratégies incluent la personnalisation à la compilation de
javac
via le processeur d'annotationAPI et génération de bytecode d’exécution.) Idéalement, les contrats seraient exécutés au moment de la compilation et, dans le pire des cas, au moyen d’une suite de tests, mais j’ai bien compris que l’application à l’exécution est mal vue. Le Checker Framework est un autre outil intéressant pour la programmation de contrats en Java .la source
default
méthodes ne peuvent pas remplacerequals
,hashCode
ettoString
. Une analyse coûts / avantages très informative expliquant pourquoi cette mesure est interdite peut être consultée ici: mail.openjdk.java.net/pipermail/lambda-dev/2013-March/…equals
une seulehashCode
méthode virtuelle et d’une seule méthode, car les collections doivent peut-être tester deux types d’égalité et les éléments qui implémenteraient plusieurs interfaces risquent de se heurter à des exigences contractuelles contradictoires. IlhashMap
est utile de pouvoir utiliser des listes qui ne vont pas changer de clé, mais il peut aussi être utile de stocker des collections dans unhashMap
répertoire qui correspond à l'équivalence plutôt qu'à l'état actuel (l'équivalence implique l'appariement de l'état et de l' immutabilité ). .Parce que vous ne pouvez hériter que d'une classe. Si vous avez deux interfaces dont les implémentations sont suffisamment complexes pour nécessiter une classe de base abstraite, ces deux interfaces s'excluent mutuellement en pratique.
L'alternative consiste à convertir ces classes de base abstraites en un ensemble de méthodes statiques et à transformer tous les champs en arguments. Cela permettrait à tout développeur de l’interface d’appeler les méthodes statiques et d’obtenir la fonctionnalité, mais c’est beaucoup, beaucoup dans un langage déjà trop bavard.
Comme exemple motivant des raisons pour lesquelles il est utile de pouvoir implémenter des interfaces, considérez cette interface Stack:
Il n'y a aucun moyen de garantir que lorsque quelqu'un implémentera l'interface,
pop
une exception sera levée si la pile est vide. Nous pourrions appliquer cette règle en séparantpop
deux méthodes: unepublic final
méthode qui applique le contrat et uneprotected abstract
méthode qui effectue le vidage.Non seulement nous nous assurons que toutes les implémentations respectent le contrat, nous les avons également libérées de l'obligation de vérifier si la pile est vide et d'exécuter l'exception. C'est une grosse victoire! ... sauf que nous avons dû changer l'interface en une classe abstraite. Dans une langue à héritage unique, cela représente une perte importante de flexibilité. Cela rend vos interfaces possibles mutuellement exclusives. Le fait de pouvoir fournir des implémentations qui ne reposent que sur les méthodes d'interface elles-mêmes résoudrait le problème.
Je ne sais pas si l'approche de Java 8 pour l'ajout de méthodes aux interfaces permet d'ajouter des méthodes finales ou des méthodes abstraites protégées, mais je sais que le langage D le permet et fournit un support natif pour Design by Contract . Il n'y a pas de danger dans cette technique car
pop
c'est final, donc aucune classe d'implémentation ne peut la remplacer.En ce qui concerne les implémentations par défaut des méthodes remplaçables, je suppose que toute implémentation par défaut ajoutée aux API Java ne repose que sur le contrat de l'interface à laquelle elles ont été ajoutées. Ainsi, toute classe qui implémente correctement l'interface se comportera également correctement avec les implémentations par défaut.
De plus,
Ce n'est pas tout à fait vrai puisque vous ne pouvez pas déclarer de champs dans une interface. Toute méthode que vous écrivez dans une interface ne peut pas compter sur des détails d'implémentation.
A titre d'exemple en faveur des méthodes statiques dans les interfaces, considérons des classes utilitaires telles que Collections dans l'API Java. Cette classe n'existe que parce que ces méthodes statiques ne peuvent pas être déclarées dans leurs interfaces respectives.
Collections.unmodifiableList
aurait pu tout aussi bien être déclaré dans l'List
interface, et il aurait été plus facile à trouver.la source
Stack
interface et que vous souhaitiez vous assurer qu'unepop
exception est lancée quand elle est appelée avec une pile vide. Etant donné les méthodes abstraitesboolean isEmpty()
etprotected T pop_impl()
, vous pouvez implémenterfinal T pop() { isEmpty()) throw PopException(); else return pop_impl(); }
Ceci applique le contrat à TOUS les développeurs.static
.L'intention était peut-être de fournir la possibilité de créer des classes mixin en remplaçant le besoin d'injecter des informations ou des fonctionnalités statiques via une dépendance.
Cette idée semble liée à la manière dont vous pouvez utiliser les méthodes d'extension en C # pour ajouter des fonctionnalités implémentées aux interfaces.
la source
list.sort(ordering);
formulaire approprié .IEnumerable
interface en C #, vous pouvez voir comment l'implémentation de méthodes d'extension à cette interface (comme leLINQ to Objects
fait le fait) ajoute des fonctionnalités à chaque classe implémentéeIEnumerable
. C'est ce que je voulais dire en ajoutant des fonctionnalités.Les deux objectifs principaux que je vois dans les
default
méthodes (certains cas d'utilisation servent les deux objectifs):S'il ne s'agissait que du second objectif, vous ne le verriez pas dans une toute nouvelle interface, telle que
Predicate
. Toutes les@FunctionalInterface
interfaces annotées doivent avoir exactement une méthode abstraite pour qu'un lambda puisse l'implémenter. Ajout desdefault
méthodes telles queand
,or
, nenegate
sont que l' utilité, et vous ne sont pas censés les remplacer. Cependant, parfois, les méthodes statiques feraient mieux .En ce qui concerne l’extension des interfaces existantes - même dans ce cas, certaines nouvelles méthodes ne sont que du sucre syntaxique. Méthodes de
Collection
commestream
,forEach
,removeIf
- essentiellement, il est tout simplement utilitaire vous n'avez pas besoin de passer outre. Et puis il y a des méthodes commespliterator
. L'implémentation par défaut est sous-optimale, mais bon, au moins le code est compilé. Utilisez-le uniquement si votre interface est déjà publiée et largement utilisée.En ce qui concerne les
static
méthodes, je suppose que les autres le couvrent assez bien: cela permet à l'interface d'être sa propre classe d'utilitaires. Peut-être pourrions-nous nous en débarrasserCollections
dans le futur de Java?Set.empty()
serait rock.la source