Est-ce que l’utilisation d’expressions Lambda est une bonne pratique java?

52

J'ai récemment maîtrisé l'expression Lambda introduite dans java 8. Je constate que chaque fois que j'utilise une interface fonctionnelle, j'ai tendance à toujours utiliser une expression Lambda au lieu de créer une classe qui implémente l'interface fonctionnelle.

Est-ce considéré comme une bonne pratique? Ou sont leurs situations où l'utilisation d'un Lambda pour une interface fonctionnelle n'est pas appropriée?

Embout d'acier
la source
122
A la question "Utilise la technique X chaque fois que possible, bonne pratique", la seule réponse correcte est "non, utilisez la technique X pas autant que possible, mais chaque fois que cela vous semble judicieux" .
Doc Brown le
Le choix du moment où utiliser un lambda (qui est anonyme) par rapport à un autre type d'implémentation fonctionnelle (par exemple une référence à une méthode) est une question vraiment intéressante. J'ai eu l'occasion de rencontrer Josh Bloch il y a quelques jours et c'est l'un des points dont il a parlé. D'après ce qu'il a dit, je crois comprendre qu'il envisage d'ajouter un nouvel élément à la prochaine édition de Effective Java, qui traite de cette question précise.
Daniel Pryden
@ DocBrown Cela devrait être une réponse, pas un commentaire.
gnasher729
1
@ gnasher729: certainement pas.
Doc Brown le

Réponses:

116

Un certain nombre de critères devraient vous inciter à ne pas utiliser de lambda:

  • Taille Plus un lambda est grand, plus il est difficile de suivre la logique qui l’entoure.
  • Répétition Il est préférable de créer une fonction nommée pour la logique répétée, bien qu'il soit correct de répéter des lambdas très simples qui sont dispersés.
  • Nommage Si vous pouvez penser à un bon nom sémantique, vous devriez l'utiliser à la place, car cela ajoute beaucoup de clarté à votre code. Je ne parle pas de noms comme priceIsOver100. x -> x.price > 100est tout aussi clair que ce nom. Je veux dire que des noms comme isEligibleVotercelui-ci remplacent une longue liste de conditions.
  • Nidification Les lambdas nichés sont vraiment très difficiles à lire.

Ne va pas trop loin. Rappelez-vous, le logiciel est facilement modifié. En cas de doute, écrivez-le dans les deux sens et voyez ce qui est le plus facile à lire.

Karl Bielefeldt
la source
1
Il est également important de considérer la quantité de données à traiter via le lambda, car les lambdas sont plutôt inefficaces pour de petites quantités de traitement de données. Ils brillent vraiment dans la parallélisation de grands ensembles de données.
CraigR8806
1
@ CraigR8806 Je ne pense pas que la performance soit une préoccupation ici. L'utilisation d'une fonction anonyme ou la création d'une classe d'extension de l'interface fonctionnelle devrait avoir les mêmes performances. Les considérations de performances entrent en ligne de compte lorsque vous parlez d’utiliser l’API Streams / Higher-Function-API sur une boucle de style impérative, mais dans cette question, nous utilisons des interfaces fonctionnelles avec une syntaxe différente dans les deux cas.
Puhlen
17
"Bien qu'il soit correct de répéter des lambdas très simples qui sont dispersés" Seulement s'ils ne changent pas nécessairement ensemble. S'ils doivent tous correspondre à la même logique pour que votre code soit correct, vous devez les mettre tous en fait de la même manière pour que les modifications ne soient effectuées qu'à un seul endroit.
jpmc26
Globalement, c'est une très bonne réponse. Mentionner l'utilisation d'une référence de méthode comme alternative à un lambda ne corrige que le trou que je vois dans cette réponse.
Morgen
3
En cas de doute, écrivez-le dans les deux sens et demandez à quelqu'un d'autre qui gère le code qui est plus facile à lire.
CorsiKa
14

Ça dépend. Lorsque vous utilisez le même lambda à différents endroits, vous devriez envisager de mettre en œuvre une classe qui implémente l'interface. Mais si vous aviez utilisé une classe interne anonyme, je pense qu'un lambda est bien meilleur.

Tohnmeister
la source
6
N'oubliez pas la possibilité d'utiliser une référence de méthode! Oracle a ajouté (et ajoute encore plus dans Java 9) des méthodes statiques et des méthodes par défaut partout pour cette raison.
Jörg W Mittag Le
13

Je soutiens la réponse de Karl Bielefeldt, mais je souhaite ajouter un bref ajout.

  • Débogage Certains IDE luttent avec la portée à l'intérieur d'un lambda et ont du mal à afficher les variables de membre dans le contexte d'un lambda. Bien que nous espérons que cette situation changera en bout de ligne, il peut être agaçant de conserver le code de quelqu'un d'autre lorsqu'il est jonché de lambdas.
Jolleyboy
la source
Vraiment vrai de .NET. Ne me force pas forcément à éviter les lambdas, mais je ne ressens certainement aucune culpabilité si je fais autre chose à la place.
user1172763
3
Si c'est le cas, vous utilisez le mauvais IDE. IntelliJ et Netbeans réussissent assez bien dans ce domaine particulier.
David Foerster
1
@ user1172763 Dans Visual Studio, si vous souhaitez afficher les variables de membre, j'ai constaté que vous pouvez remonter la pile d'appels jusqu'à atteindre le contexte du lambda.
Zev Spitz
6

Accès aux variables locales de la portée englobante

La réponse acceptée par Karl Bielefeldt est correcte. Je peux ajouter une distinction supplémentaire:

  • Portée

Le code lambda imbriqué dans une méthode dans une classe peut accéder à toutes les variables finales trouvées dans cette méthode et cette classe.

Créer une classe qui implémente l'interface fonctionnelle ne vous donne pas un tel accès direct à l'état du code appelant.

Pour citer le tutoriel Java (l'emphase mienne):

Comme les classes locales et anonymes, les expressions lambda peuvent capturer des variables; ils ont le même accès aux variables locales de la portée englobante . Cependant, contrairement aux classes locales et anonymes, les expressions lambda ne présentent pas de problème d'observation (reportez-vous à la section Observation pour plus d'informations). Les expressions lambda ont une portée lexicale. Cela signifie qu'ils n'héritent d'aucun nom d'un supertype ni n'introduisent un nouveau niveau de périmètre. Les déclarations dans une expression lambda sont interprétées exactement comme dans l'environnement englobant.

Ainsi, s'il est avantageux d'extraire un code long et de le nommer, vous devez le comparer à la simplicité de l'accès direct à l'état de la méthode et de la classe englobantes.

Voir:

Basil Bourque
la source
4

C'est peut-être une énigme, mais à tous les autres excellents points soulevés dans d'autres réponses, j'ajouterais:

Préférez les références de méthode lorsque cela est possible. Comparer:

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);

contre

employees.stream()
         .map(employee -> employee.getName())
         .forEach(employeeName -> System.out.println(employeeName));

L'utilisation d'une référence de méthode vous évite d'avoir à nommer le ou les arguments de lambda, qui sont généralement redondants et / ou conduisent à des noms paresseux tels que eou x.

Matt McHenry
la source
0

En se basant sur la réponse de Matt McHenry, il peut exister une relation entre lambda et les références de méthode, le lambda pouvant être réécrit sous la forme d'une série de références de méthodes. Par exemple:

employees.stream()
         .forEach(employeeName -> System.out.println(employee.getName()));

contre

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);
LaFayette
la source