Lequel des éléments suivants est une meilleure pratique dans Java 8?
Java 8:
joins.forEach(join -> mIrc.join(mSession, join));
Java 7:
for (String join : joins) {
mIrc.join(mSession, join);
}
J'ai beaucoup de boucles for qui pourraient être "simplifiées" avec les lambdas, mais y a-t-il vraiment un avantage à les utiliser? Cela améliorerait-il leurs performances et leur lisibilité?
ÉDITER
Je vais également étendre cette question à des méthodes plus longues. Je sais que vous ne pouvez pas retourner ou casser la fonction parent d'un lambda et cela devrait également être pris en considération lors de la comparaison, mais y a-t-il autre chose à considérer?
java
for-loop
java-8
java-stream
nebkat
la source
la source
joins.forEach((join) -> mIrc.join(mSession, join));
vraiment une "simplification" defor (String join : joins) { mIrc.join(mSession, join); }
? Vous avez augmenté le nombre de signes de ponctuation de 9 à 12, afin de masquer le type dejoin
. Ce que vous avez vraiment fait, c'est de mettre deux déclarations sur une seule ligne.Réponses:
La meilleure pratique consiste à utiliser
for-each
. En plus de violer le principe Keep It Simple, Stupid , le nouveau modèleforEach()
présente au moins les lacunes suivantes:Impossible d'utiliser des variables non finales . Ainsi, un code comme celui-ci ne peut pas être transformé en lambda forEach:
Impossible de gérer les exceptions vérifiées . Il n'est en fait pas interdit aux lambdas de lancer des exceptions vérifiées, mais les interfaces fonctionnelles courantes comme
Consumer
n'en déclarent aucune. Par conséquent, tout code qui lève des exceptions vérifiées doit les encapsuler danstry-catch
ouThrowables.propagate()
. Mais même si vous faites cela, ce qui arrive à l'exception levée n'est pas toujours clair. Il pourrait être avalé quelque part dans les tripes deforEach()
Contrôle de débit limité . A
return
dans un lambda est égal à acontinue
dans un pour chacun, mais il n'y a pas d'équivalent à abreak
. Il est également difficile de faire des choses comme des valeurs de retour, un court-circuit ou des drapeaux (ce qui aurait un peu allégé les choses si ce n'était pas une violation de la règle des variables non finales ). "Ce n'est pas seulement une optimisation, mais critique lorsque vous considérez que certaines séquences (comme la lecture des lignes dans un fichier) peuvent avoir des effets secondaires, ou vous pouvez avoir une séquence infinie."Pourrait s'exécuter en parallèle , ce qui est une chose horrible, horrible pour tous sauf 0,1% de votre code qui doit être optimisé. Tout code parallèle doit être réfléchi (même s'il n'utilise pas de verrous, de volatils et d'autres aspects particulièrement désagréables de l'exécution multithread traditionnelle). Tout bug sera difficile à trouver.
Cela pourrait nuire aux performances , car le JIT ne peut pas optimiser forEach () + lambda dans la même mesure que les boucles simples, surtout maintenant que les lambdas sont nouveaux. Par «optimisation», je ne parle pas de la surcharge de l'appel de lambdas (qui est petite), mais de l'analyse et de la transformation sophistiquées que le compilateur JIT moderne effectue sur l'exécution de code.
Si vous avez besoin du parallélisme, il est probablement beaucoup plus rapide et pas beaucoup plus difficile d'utiliser un ExecutorService . Les flux sont à la fois automagiques (lire: ne connaissent pas grand-chose à votre problème) et utilisent une stratégie de parallélisation spécialisée (lire: inefficace pour le cas général) ( décomposition récursive à jointure en fourche ).
Rend le débogage plus confus , en raison de la hiérarchie des appels imbriqués et, Dieu nous en préserve, de l'exécution parallèle. Le débogueur peut avoir des problèmes d'affichage des variables du code environnant, et des choses comme la procédure pas à pas peuvent ne pas fonctionner comme prévu.
Les flux en général sont plus difficiles à coder, à lire et à déboguer . En fait, cela est vrai des API complexes " fluent " en général. La combinaison d'instructions simples complexes, l'utilisation intensive de génériques et le manque de variables intermédiaires concourent à produire des messages d'erreur déroutants et à contrecarrer le débogage. Au lieu de "cette méthode n'a pas de surcharge pour le type X", vous obtenez un message d'erreur plus proche de "quelque part où vous avez foiré les types, mais nous ne savons pas où ni comment". De même, vous ne pouvez pas parcourir et examiner les choses dans un débogueur aussi facilement que lorsque le code est divisé en plusieurs instructions et que les valeurs intermédiaires sont enregistrées dans des variables. Enfin, la lecture du code et la compréhension des types et des comportements à chaque étape de l'exécution peuvent être non triviales.
Sort comme un pouce endolori . Le langage Java possède déjà l'instruction for-each. Pourquoi le remplacer par un appel de fonction? Pourquoi encourager à cacher des effets secondaires quelque part dans les expressions? Pourquoi encourager les one-liners peu maniables? Mélanger régulièrement pour chacun et nouveau pour chaque bon gré mal gré est un mauvais style. Le code doit parler en idiomes (des schémas qui sont faciles à comprendre en raison de leur répétition), et moins les idiomes sont utilisés, plus le code est clair et moins de temps est consacré à décider quel idiome utiliser (une grosse perte de temps pour les perfectionnistes comme moi! ).
Comme vous pouvez le voir, je ne suis pas un grand fan de forEach () sauf dans les cas où cela a du sens.
Un fait particulièrement choquant pour moi est le fait qu'il
Stream
n'implémente pasIterable
(bien qu'il ait réellement une méthodeiterator
) et ne puisse pas être utilisé dans un for-each, uniquement avec un forEach (). Je recommande de diffuser des flux dans Iterables avec(Iterable<T>)stream::iterator
. Une meilleure alternative consiste à utiliser StreamEx qui résout un certain nombre de problèmes d'API Stream, y compris la mise en œuvreIterable
.Cela dit,
forEach()
est utile pour les éléments suivants:Itération atomique sur une liste synchronisée . Avant cela, une liste générée avec
Collections.synchronizedList()
était atomique par rapport à des choses comme get ou set, mais n'était pas thread-safe lors de l'itération.Exécution parallèle (en utilisant un flux parallèle approprié) . Cela vous permet d'économiser quelques lignes de code par rapport à l'utilisation d'un ExecutorService, si votre problème correspond aux hypothèses de performances intégrées dans Streams et Spliterators.
Conteneurs spécifiques qui , comme la liste synchronisée, bénéficient du contrôle de l'itération (bien que cela soit en grande partie théorique à moins que les gens puissent apporter plus d'exemples)
Appeler une seule fonction plus proprement en utilisant
forEach()
et un argument de référence de méthode (c.-àlist.forEach (obj::someMethod)
-d.). Cependant, gardez à l'esprit les points sur les exceptions vérifiées, le débogage plus difficile et la réduction du nombre d'idiomes que vous utilisez lors de l'écriture de code.Articles que j'ai utilisés comme référence:
EDIT: On dirait que certaines des propositions originales pour les lambdas (telles que http://www.javac.info/closures-v06a.html ) ont résolu certains des problèmes que j'ai mentionnés (tout en ajoutant leurs propres complications, bien sûr).
la source
forEach
est là pour encourager le style fonctionnel, c'est-à-dire utiliser des expressions sans effets secondaires. Si vous rencontrez la situation, le theforEach
ne fonctionne pas bien avec vos effets secondaires, vous devriez avoir le sentiment que vous n'utilisez pas le bon outil pour le travail. Ensuite, la réponse est simple, c'est parce que votre sentiment est bon, alors restez à la boucle pour chaque pour cela. Lafor
boucle classique n'est pas devenue obsolète…forEach
on l'utiliser sans effets secondaires?forEach
c'est la seule opération de flux destinée aux effets secondaires, mais pas aux effets secondaires comme votre exemple de code, le comptage est unereduce
opération typique . Je suggère, en règle générale, de conserver chaque opération qui manipule des variables locales ou qui influencera le flux de contrôle (y compris la gestion des exceptions) dans unefor
boucle classique . En ce qui concerne la question d'origine, je pense que le problème vient du fait que quelqu'un utilise un flux où une simplefor
boucle sur la source du flux serait suffisante. Utilisez un flux oùforEach()
fonctionne uniquementforEach
conviendrait-il?for
boucles.L'avantage est pris en compte lorsque les opérations peuvent être exécutées en parallèle. (Voir http://java.dzone.com/articles/devoxx-2012-java-8-lambda-and - la section sur l'itération interne et externe)
Le principal avantage de mon point de vue est que la mise en œuvre de ce qui doit être fait dans la boucle peut être définie sans avoir à décider si elle sera exécutée en parallèle ou séquentielle
Si vous voulez que votre boucle soit exécutée en parallèle, vous pouvez simplement écrire
Vous devrez écrire du code supplémentaire pour la gestion des threads, etc.
Remarque: Pour ma réponse, j'ai supposé que les jointures implémentaient l'
java.util.Stream
interface. Si join implémente uniquement l'java.util.Iterable
interface, cela n'est plus vrai.la source
map
&fold
qui ne sont pas vraiment liées aux lambdas.Stream#forEach
et ceIterable#forEach
n'est pas la même chose. OP demandeIterable#forEach
.joins
mise en œuvreIterable
au lieu deStream
? D'après deux oujoins.stream().forEach((join) -> mIrc.join(mSession, join));
joins.parallelStream().forEach((join) -> mIrc.join(mSession, join));
joins
Iterable
En lisant cette question, on peut avoir l'impression qu'en
Iterable#forEach
combinaison avec des expressions lambda est un raccourci / remplacement pour écrire une boucle traditionnelle pour chaque. Ce n'est tout simplement pas vrai. Ce code de l'OP:n'est pas conçu comme un raccourci pour écrire
et ne doit certainement pas être utilisé de cette façon. Au lieu de cela, il est conçu comme un raccourci (bien qu'il ne soit pas exactement le même) pour écrire
Et c'est en remplacement du code Java 7 suivant:
Le remplacement du corps d'une boucle par une interface fonctionnelle, comme dans les exemples ci-dessus, rend votre code plus explicite: vous dites que (1) le corps de la boucle n'affecte pas le code environnant et le flux de contrôle, et (2) le le corps de la boucle peut être remplacé par une implémentation différente de la fonction, sans affecter le code environnant. Ne pas pouvoir accéder aux variables non finales de la portée externe n'est pas un déficit de fonctions / lambdas, c'est une caractéristique qui distingue la sémantique de
Iterable#forEach
de la sémantique d'une boucle traditionnelle pour chaque boucle. Une fois que l'on s'habitue à la syntaxe deIterable#forEach
, cela rend le code plus lisible, car vous obtenez immédiatement ces informations supplémentaires sur le code.Les boucles traditionnelles pour chaque resteront certainement bonne pratique (pour éviter le terme galvaudé de " meilleure pratique ") en Java. Mais cela ne signifie pas que cela
Iterable#forEach
devrait être considéré comme une mauvaise pratique ou un mauvais style. C'est toujours une bonne pratique, d'utiliser le bon outil pour faire le travail, et cela comprend le mélange de boucles traditionnelles pour chaque avecIterable#forEach
, lorsque cela est logique.Depuis les inconvénients de
Iterable#forEach
ont déjà été discutés dans ce fil, voici quelques raisons, pourquoi vous voudrez probablement utiliserIterable#forEach
:Pour rendre votre code plus explicite: comme décrit ci-dessus,
Iterable#forEach
peut rendre votre code plus explicite et lisible dans certaines situations.Pour rendre votre code plus extensible et maintenable: utilisation d'une fonction comme corps d'une boucle vous permet de remplacer cette fonction par différentes implémentations (voir Modèle de stratégie ). Vous pouvez par exemple facilement remplacer l'expression lambda par un appel de méthode, qui peut être remplacé par des sous-classes:
Ensuite, vous pouvez fournir des stratégies par défaut à l'aide d'une énumération qui implémente l'interface fonctionnelle. Cela rend non seulement votre code plus extensible, mais augmente également la maintenabilité car il dissocie l'implémentation de la boucle de la déclaration de boucle.
Pour rendre votre code plus déboguable: La séparation de l'implémentation de la boucle de la déclaration peut également rendre le débogage plus facile, car vous pourriez avoir une implémentation de débogage spécialisée, qui affiche les messages de débogage, sans avoir à encombrer votre code principal avec
if(DEBUG)System.out.println()
. L'implémentation de débogage pourrait par exemple être un délégué , qui décore l'implémentation de la fonction réelle.Pour optimiser le code critique pour les performances: contrairement à certaines des affirmations de ce thread,
Iterable#forEach
fournit déjà de meilleures performances qu'une boucle traditionnelle pour chaque boucle, au moins lors de l'utilisation d'ArrayList et de l'exécution de Hotspot en mode "-client". Bien que cette amélioration des performances soit faible et négligeable dans la plupart des cas d'utilisation, il existe des situations où ces performances supplémentaires peuvent faire la différence. Par exemple, les responsables de bibliothèque voudront certainement évaluer si certaines de leurs implémentations de boucle existantes doivent être remplacées parIterable#forEach
.Pour étayer cette déclaration avec des faits, j'ai fait quelques micro-repères avec Caliper . Voici le code de test (le dernier Caliper de git est nécessaire):
Et voici les résultats:
Lorsqu'il s'exécute avec "-client",
Iterable#forEach
surpasse la boucle for traditionnelle sur une liste de tableaux, mais est toujours plus lent que l'itération directe sur un tableau. Lors de l'exécution avec "-server", les performances de toutes les approches sont à peu près les mêmes.Pour fournir un support optionnel pour l'exécution parallèle: Il a déjà été dit ici, que la possibilité d'exécuter l'interface fonctionnelle
Iterable#forEach
en parallèle en utilisant des flux , est certainement un aspect important. PuisqueCollection#parallelStream()
ne garantit pas que la boucle est réellement exécutée en parallèle, il faut considérer cela comme une fonctionnalité facultative . En parcourant votre liste aveclist.parallelStream().forEach(...);
, vous dites explicitement: Cette boucle prend en charge l' exécution parallèle, mais elle n'en dépend pas. Encore une fois, c'est une caractéristique et non un déficit!En éloignant la décision d'exécution parallèle de votre implémentation de boucle réelle, vous autorisez l'optimisation facultative de votre code, sans affecter le code lui-même, ce qui est une bonne chose. De plus, si l'implémentation de flux parallèle par défaut ne correspond pas à vos besoins, personne ne vous empêche de fournir votre propre implémentation. Vous pouvez par exemple fournir une collection optimisée en fonction du système d'exploitation sous-jacent, de la taille de la collection, du nombre de cœurs et de certains paramètres de préférence:
La bonne chose ici est que votre implémentation de boucle n'a pas besoin de connaître ou de se soucier de ces détails.
la source
forEach
et enfor-each
fonction de certains critères concernant la nature du corps de boucle. La sagesse et la discipline pour suivre de telles règles sont la marque d'un bon programmeur. Ces règles sont également son fléau, car les gens autour de lui ne les respectent pas ou ne sont pas d'accord. Par exemple, en utilisant des exceptions cochées et non cochées. Cette situation semble encore plus nuancée. Mais, si le corps "n'affecte pas le code surround ou le contrôle de flux", n'est-il pas mieux pris en compte comme fonction?But, if the body "does not affect surround code or flow control," isn't factoring it out as a function better?
. Oui, ce sera souvent le cas à mon avis - la factorisation de ces boucles en tant que fonctions est une conséquence naturelle.Iterable#forEach
avant Java 8 uniquement en raison de l'augmentation des performances. Le projet en question a une boucle principale similaire à une boucle de jeu, avec un nombre indéfini de sous-boucles imbriquées, où les clients peuvent brancher les participants de la boucle en tant que fonctions. Une telle structure logicielle en bénéficie grandementIteable#forEach
.forEach()
peut être implémenté pour être plus rapide que pour chaque boucle, car l'itérable connaît la meilleure façon d'itérer ses éléments, par opposition à l'itérateur standard. La différence est donc une boucle interne ou une boucle externe.Par exemple,
ArrayList.forEach(action)
peut être simplement implémenté commepar opposition à la boucle pour chaque qui nécessite beaucoup d'échafaudages
Cependant, nous devons également tenir compte de deux frais généraux en utilisant
forEach()
, l'un crée l'objet lambda, l'autre appelle la méthode lambda. Ils ne sont probablement pas significatifs.voir également http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/ pour comparer les itérations internes / externes pour différents cas d'utilisation.
la source
void forEach(Consumer<T> v){leftTree.forEach(v);v.accept(rootElem);rightTree.forEach(v);}
c'est plus élégant que l'itération externe, et vous pouvez décider de la meilleure façon de synchroniserString.join
méthodes (ok, mauvaise jointure) est "Nombre d'éléments ne valant probablement pas la surcharge Arrays.stream". ils utilisent donc une boucle chic pour.TL; DR :
List.stream().forEach()
était le plus rapide.Je pensais que je devais ajouter mes résultats de l'itération de référence. J'ai adopté une approche très simple (pas de frameworks de benchmarking) et j'ai testé 5 méthodes différentes:
for
List.forEach()
List.stream().forEach()
List.parallelStream().forEach
la procédure de test et les paramètres
La liste de cette classe doit être itérée et
doIt(Integer i)
appliquée à tous ses membres, à chaque fois via une méthode différente. dans la classe principale, j'exécute la méthode testée trois fois pour réchauffer la machine virtuelle Java. Je lance ensuite la méthode de test 1000 fois en additionnant le temps qu'il faut pour chaque méthode d'itération (en utilisantSystem.nanoTime()
). Après cela, je divise cette somme par 1000 et c'est le résultat, le temps moyen. exemple:Je l'ai exécuté sur un processeur i5 4 cœurs, avec la version java 1.8.0_05
classique
for
temps d'exécution: 4,21 ms
foreach classique
temps d'exécution: 5,95 ms
List.forEach()
temps d'exécution: 3,11 ms
List.stream().forEach()
temps d'exécution: 2,79 ms
List.parallelStream().forEach
temps d'exécution: 3,6 ms
la source
System.out.println
vous affichez simplement ces données naïvement, tous les résultats sont inutiles.System.nanoTime()
. Si vous lisez la réponse, vous verrez comment cela a été fait. Je ne pense pas que cela le rend inutile car il s'agit d'une question relative . Je me fiche de la qualité d'une certaine méthode, je me soucie de son efficacité par rapport aux autres méthodes.Je sens que je dois étendre un peu mon commentaire ...
À propos du paradigme \ style
C'est probablement l'aspect le plus notable. La FP est devenue populaire en raison de ce que vous pouvez éviter les effets secondaires. Je ne vais pas approfondir les avantages que vous pouvez en retirer, car cela n'est pas lié à la question.
Cependant, je dirai que l'itération utilisant Iterable.forEach est inspirée de FP et résulte plutôt d'apporter plus de FP à Java (ironiquement, je dirais qu'il n'y a pas beaucoup d'utilisation pour forEach en FP pur, car il ne fait rien d'autre que l'introduction Effets secondaires).
En fin de compte, je dirais que c'est plutôt une question de goût \ style \ paradigme que vous écrivez actuellement.
À propos du parallélisme.
Du point de vue des performances, il n'y a aucun avantage notable promis à utiliser Iterable.forEach over foreach (...).
Selon les documents officiels sur Iterable.forEach :
... c'est-à-dire que les documents indiquent clairement qu'il n'y aura pas de parallélisme implicite. L'ajout d'un serait une violation de LSP.
Maintenant, il y a des "collections parallèles" qui sont promises dans Java 8, mais pour travailler avec celles dont vous avez besoin, je dois être plus explicite et mettre un soin supplémentaire à les utiliser (voir la réponse de mschenk74 par exemple).
BTW: dans ce cas Stream.forEach sera utilisé, et cela ne garantit pas que le travail réel sera effectué en parallèle (dépend de la collection sous-jacente).
MISE À JOUR: ce n'est peut-être pas si évident et un peu étiré en un coup d'œil, mais il y a une autre facette du style et de la lisibilité.
Tout d'abord - les anciens forloops simples sont simples et anciens. Tout le monde les connaît déjà.
Deuxièmement, et plus important - vous voudrez probablement utiliser Iterable.forEach uniquement avec des lambdas à une ligne. Si le «corps» devient plus lourd - ils ont tendance à ne pas être si lisibles. Vous avez 2 options à partir d'ici - utilisez des classes internes (beurk) ou utilisez un ancien forloop ordinaire. Les gens sont souvent ennuyés quand ils voient les mêmes choses (itérations sur les collections) être effectuées dans différents styles / styles dans la même base de code, et cela semble être le cas.
Encore une fois, cela pourrait ou non être un problème. Dépend des personnes travaillant sur le code.
la source
collection.forEach(MyClass::loopBody);
L'une des fonctions les plus agréables
forEach
les est le manque de prise en charge des exceptions vérifiées.Une solution de contournement possible consiste à remplacer le terminal
forEach
par une ancienne boucle foreach simple:Voici la liste des questions les plus populaires avec d'autres solutions de contournement sur la gestion des exceptions vérifiées dans les lambdas et les flux:
Fonction Java 8 Lambda qui lève une exception?
Java 8: Lambda-Streams, filtrer par méthode avec exception
Comment puis-je lever des exceptions CHECKED depuis des flux Java 8?
Java 8: Traitement obligatoire des exceptions vérifiées dans les expressions lambda. Pourquoi obligatoire, pas facultatif?
la source
L'avantage de la méthode Java 1.8 forEach sur 1.7 Enhanced for loop est que lors de l'écriture de code, vous pouvez vous concentrer uniquement sur la logique métier.
La méthode forEach prend l'argument java.util.function.Consumer comme argument, ce qui permet d'avoir notre logique métier à un emplacement séparé que vous pouvez réutiliser à tout moment.
Regardez l'extrait ci-dessous,
Ici, j'ai créé une nouvelle classe qui remplacera la méthode de classe acceptée de Consumer Class, où vous pouvez ajouter des fonctionnalités supplémentaires, plus que l'itération .. !!!!!!
la source