Méthodes par défaut de Java 8 en tant que traits

116

Est-ce une pratique sûre d'utiliser des méthodes par défaut comme une version pauvre des traits dans Java 8?

Certains prétendent que cela peut rendre les pandas tristes si vous les utilisez juste pour le plaisir, parce que c'est cool, mais ce n'est pas mon intention. Il est également souvent rappelé que des méthodes par défaut ont été introduites pour prendre en charge l'évolution des API et la compatibilité descendante, ce qui est vrai, mais cela ne rend pas faux ou tordu de les utiliser comme traits en soi.

J'ai à l'esprit le cas d'utilisation pratique suivant :

public interface Loggable {
    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

Ou peut-être définir un PeriodTrait:

public interface PeriodeTrait {
    Date getStartDate();
    Date getEndDate();
    default isValid(Date atDate) {
        ...
    }
}

Certes, la composition pourrait être utilisée (ou même des classes d'aide) mais elle semble plus verbeuse et encombrée et ne permet pas de bénéficier du polymorphisme.

Alors, est-il correct / sûr d'utiliser les méthodes par défaut comme traits de base , ou devrais-je m'inquiéter des effets secondaires imprévus?

Plusieurs questions sur le SO sont liées aux traits Java vs Scala; ce n'est pas le point ici. Je ne demande pas seulement des opinions non plus. Au lieu de cela, je recherche une réponse faisant autorité ou au moins un aperçu du terrain: si vous avez utilisé des méthodes par défaut comme caractéristiques de votre projet d'entreprise, est-ce que cela s'est avéré être une bombe à retardement?

youri
la source
Il me semble que vous pourriez obtenir les mêmes avantages en héritant d'une classe abstraite et ne pas avoir à vous soucier de faire pleurer les pandas ... La seule raison que je vois pour utiliser une méthode par défaut dans une interface est que vous avez besoin de la fonctionnalité et que vous ne pouvez pas modifier un tas de code hérité basé sur l'interface.
Deven Phillips
1
Je suis d'accord avec @ infosec812 sur l'extension d'une classe abstraite qui définit son propre champ de journalisation statique. Votre méthode logger () n'instancierait-elle pas une nouvelle instance de journalisation à chaque fois qu'elle est appelée?
Eidan Spiegel
Pour la journalisation, vous pouvez consulter Projectlombok.org et leur annotation @ Slf4j.
Deven Phillips

Réponses:

120

La réponse courte est: c'est sûr si vous les utilisez en toute sécurité :)

La réponse sournoise: dites-moi ce que vous entendez par traits, et peut-être que je vais vous donner une meilleure réponse :)

Sérieusement, le terme «trait» n'est pas bien défini. De nombreux développeurs Java sont plus familiers avec les traits tels qu'ils sont exprimés en Scala, mais Scala est loin d'être le premier langage à avoir des traits, que ce soit en nom ou en effet.

Par exemple, dans Scala, les traits sont avec état (peuvent avoir des varvariables); dans Fortress, ce sont des comportements purs. Les interfaces de Java avec les méthodes par défaut sont sans état; cela veut-il dire que ce ne sont pas des traits? (Indice: c'était une question piège.)

Encore une fois, dans Scala, les traits sont composés par linéarisation; si la classe Aétend les traits Xet Y, alors l'ordre dans lequel Xet Ysont mélangés détermine comment les conflits entre Xet Ysont résolus. En Java, ce mécanisme de linéarisation n'est pas présent (il a été rejeté, en partie, car il était trop "différent de Java".)

La raison immédiate de l'ajout de méthodes par défaut aux interfaces était de prendre en charge l' évolution des interfaces , mais nous étions bien conscients que nous allions plus loin. Que vous considériez cela comme une "évolution d'interface ++" ou des "traits -" est une question d'interprétation personnelle. Donc, pour répondre à votre question sur la sécurité ... tant que vous vous en tenez à ce que le mécanisme soutient réellement, plutôt que d'essayer de l'étirer à souhait à quelque chose qu'il ne supporte pas, tout devrait aller bien.

Un objectif clé de la conception était que, du point de vue du client d'une interface, les méthodes par défaut ne devraient pas être distinguées des méthodes d'interface "régulières". La valeur par défaut d'une méthode n'est donc intéressante que pour le concepteur et l' implémenteur de l'interface.

Voici quelques cas d'utilisation qui correspondent bien aux objectifs de conception:

  • Évolution de l'interface. Ici, nous ajoutons une nouvelle méthode à une interface existante, qui a une implémentation par défaut sensible en termes de méthodes existantes sur cette interface. Un exemple serait l'ajout de la forEachméthode à Collection, où l'implémentation par défaut est écrite en termes de iterator()méthode.

  • Méthodes "facultatives". Ici, le concepteur d'une interface dit: "Les réalisateurs n'ont pas besoin d'implémenter cette méthode s'ils veulent vivre avec les limitations de fonctionnalités que cela implique". Par exemple, Iterator.removea reçu une valeur par défaut qui jette UnsupportedOperationException; puisque la grande majorité des implémentations Iteratoront de toute façon ce comportement, la valeur par défaut rend cette méthode essentiellement facultative. (Si le comportement de AbstractCollectionétait exprimé par défaut sur Collection, nous pourrions faire de même pour les méthodes mutatives.)

  • Méthodes de commodité. Ce sont des méthodes strictement pratiques, à nouveau généralement implémentées en termes de méthodes non par défaut sur la classe. La logger()méthode de votre premier exemple en est une illustration raisonnable.

  • Combinateurs. Ce sont des méthodes de composition qui instancient de nouvelles instances de l'interface en fonction de l'instance actuelle. Par exemple, les méthodes Predicate.and()ou Comparator.thenComparing()sont des exemples de combinateurs.

Si vous fournissez une implémentation par défaut, vous devez également fournir une spécification pour @implSpecla méthode par défaut (dans le JDK, nous utilisons la balise javadoc pour cela) pour aider les implémenteurs à comprendre s'ils veulent remplacer la méthode ou non. Certaines valeurs par défaut, comme les méthodes pratiques et les combinateurs, ne sont presque jamais remplacées; d'autres, comme les méthodes facultatives, sont souvent remplacées. Vous devez fournir suffisamment de spécification (pas seulement de la documentation) sur ce que la valeur par défaut promet de faire, afin que l'implémenteur puisse prendre une décision judicieuse pour savoir s'il doit le remplacer.

Brian Goetz
la source
9
Merci Brian pour cette réponse complète. Maintenant, je peux utiliser les méthodes par défaut avec un cœur léger. Lecteurs: plus d'informations de Brian Goetz sur l'évolution de l'interface et les méthodes par défaut peuvent être trouvées dans NightHacking Worldwide Lambdas par exemple.
youri
Merci @ brian-goetz. D'après ce que vous avez dit, je pense que les méthodes par défaut de Java sont plus proches de la notion de traits traditionnels tels que définis dans l'article de Ducasse et al ( scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf ). Les «traits» de Scala ne me semblent pas du tout des traits, puisqu'ils ont un état, utilisent la linéarisation dans leur composition, et il semblerait qu'ils résolvent aussi implicitement les conflits de méthodes - et ce sont toutes les choses que les traits traditionnels ne font pas. avoir. En fait, je dirais que les traits Scala sont plus des mixins que des traits. Qu'est-ce que tu penses? PS: je n'ai jamais codé en Scala.
adino
Que diriez-vous d'utiliser des implémentations par défaut vides pour les méthodes dans une interface qui agit comme un écouteur? L'implémentation de l'écouteur peut ne s'intéresser qu'à l'écoute de quelques méthodes d'interface, donc en définissant les méthodes par défaut, l'implémenteur n'a qu'à implémenter les méthodes dont il a besoin pour écouter.
Lahiru Chandima
1
@LahiruChandima Comme avec MouseListener? Dans la mesure où ce style d'API a du sens, il s'inscrit dans le compartiment "méthodes facultatives". Assurez-vous de documenter clairement les options!
Brian Goetz
Oui. Tout comme dans MouseListener. Merci pour la réponse.
Lahiru Chandima