Pour les cas simples comme celui illustré, ils sont généralement les mêmes. Cependant, il existe un certain nombre de différences subtiles qui pourraient être importantes.
Un problème est lié à la commande. Avec Stream.forEach
, la commande n'est pas définie . Il est peu probable que cela se produise avec des flux séquentiels, cependant, c'est dans la spécification pour Stream.forEach
s'exécuter dans un ordre arbitraire. Cela se produit fréquemment dans les flux parallèles. En revanche, Iterable.forEach
est toujours exécuté dans l'ordre d'itération du Iterable
, si un est spécifié.
Un autre problème concerne les effets secondaires. L'action spécifiée dans Stream.forEach
doit être sans interférence . (Voir la documentation du package java.util.stream .) A Iterable.forEach
potentiellement moins de restrictions. Pour les collections dans java.util
, Iterable.forEach
utilisera généralement cette collection Iterator
, dont la plupart sont conçues pour être rapides et qui jetteront ConcurrentModificationException
si la collection est structurellement modifiée pendant l'itération. Cependant, les modifications qui ne sont pas structurelles sont autorisées pendant l'itération. Par exemple, la documentation de la classe ArrayList indique que "la simple définition de la valeur d'un élément n'est pas une modification structurelle". Ainsi, l'action pourArrayList.forEach
est autorisé à définir des valeurs dans le sous-jacent ArrayList
sans problème.
Les collections simultanées sont encore différentes. Au lieu d'être rapides, ils sont conçus pour être faiblement cohérents . La définition complète est à ce lien. En bref, cependant, réfléchissez ConcurrentLinkedDeque
. L'action passée à sa forEach
méthode est autorisée à modifier le deque sous-jacent, même structurellement, et ConcurrentModificationException
n'est jamais levée. Cependant, la modification qui se produit peut ou non être visible dans cette itération. (D'où la cohérence "faible".)
Une autre différence est encore visible si Iterable.forEach
itère sur une collection synchronisée. Sur une telle collection, Iterable.forEach
prend une fois le verrou de la collection et le maintient sur tous les appels à la méthode d'action. L' Stream.forEach
appel utilise le séparateur de la collection, qui ne se verrouille pas et qui repose sur la règle de non-interférence en vigueur. La collection qui sauvegarde le flux peut être modifiée pendant l'itération, et si c'est le cas, un ConcurrentModificationException
comportement incohérent ou peut en résulter.
Iterable.forEach takes the collection's lock
. D'où proviennent ces informations? Je ne parviens pas à trouver un tel comportement dans les sources JDK.ArrayList
ont une vérification assez stricte des modifications simultanées, sont donc souvent lancéesConcurrentModificationException
. Mais cela n'est pas garanti, en particulier pour les flux parallèles. Au lieu de CME, vous pourriez obtenir une réponse inattendue. Pensez également aux modifications non structurelles de la source du flux. Pour les flux parallèles, vous ne savez pas quel thread traitera un élément particulier, ni s'il a été traité au moment de sa modification. Cela met en place une condition de concurrence, où vous pouvez obtenir des résultats différents à chaque course, et ne jamais obtenir un CME.Cette réponse concerne les performances des différentes implémentations des boucles. Ce n'est que marginalement pertinent pour les boucles qui sont appelées TRÈS SOUVENT (comme des millions d'appels). Dans la plupart des cas, le contenu de la boucle sera de loin l'élément le plus cher. Pour les situations où vous bouclez très souvent, cela peut toujours être intéressant.
Vous devez répéter ces tests sous le système cible car cela est spécifique à l'implémentation ( code source complet ).
J'exécute openjdk version 1.8.0_111 sur une machine Linux rapide.
J'ai écrit un test qui boucle 10 ^ 6 fois sur une liste en utilisant ce code avec différentes tailles pour
integers
(10 ^ 0 -> 10 ^ 5 entrées).Les résultats sont ci-dessous, la méthode la plus rapide varie en fonction du nombre d'entrées dans la liste.
Mais toujours dans les pires situations, boucler plus de 10 ^ 5 entrées 10 ^ 6 fois a pris 100 secondes pour le moins performant, donc d'autres considérations sont plus importantes dans pratiquement toutes les situations.
Voici mes timings: millisecondes / fonction / nombre d'entrées dans la liste. Chaque exécution est de 10 ^ 6 boucles.
Si vous répétez l'expérience, j'ai publié le code source complet . Veuillez modifier cette réponse et ajouter vos résultats avec une notation du système testé.
À l'aide d'un MacBook Pro, Intel Core i7 2,5 GHz, 16 Go, macOS 10.12.6:
Java 8 Hotspot VM - Intel Xeon 3,4 GHz, 8 Go, Windows 10 Pro
Java 11 Hotspot VM - Intel Xeon 3,4 GHz, 8 Go, Windows 10 Pro
(même machine que ci-dessus, version JDK différente)
Java 11 OpenJ9 VM - Intel Xeon 3,4 GHz, 8 Go, Windows 10 Pro
(même machine et version JDK que ci-dessus, VM différente)
Java 8 Hotspot VM - 2,8 GHz AMD, 64 Go, Windows Server 2016
Java 11 Hotspot VM - 2,8 GHz AMD, 64 Go, Windows Server 2016
(même machine que ci-dessus, version JDK différente)
Java 11 OpenJ9 VM - 2,8 GHz AMD, 64 Go, Windows Server 2016
(même machine et version JDK que ci-dessus, VM différente)
L'implémentation de VM que vous choisissez fait également une différence Hotspot / OpenJ9 / etc.
la source
Il n'y a aucune différence entre les deux que vous avez mentionnés, du moins sur le plan conceptuel, ce
Collection.forEach()
n'est qu'un raccourci.En interne, la
stream()
version a un peu plus de surcharge en raison de la création d'objets, mais en regardant le temps d'exécution, elle n'a pas non plus de surcharge.Les deux implémentations finissent par itérer
collection
une fois sur le contenu et, pendant l'itération, impriment l'élément.la source
Stream
création ou des objets individuels? AFAIK, aStream
ne duplique pas les éléments.Collection.forEach () utilise l'itérateur de la collection (s'il est spécifié). Cela signifie que l'ordre de traitement des articles est défini. En revanche, l'ordre de traitement de Collection.stream (). ForEach () n'est pas défini.
Dans la plupart des cas, cela ne fait aucune différence lequel des deux nous choisissons. Les flux parallèles nous permettent d'exécuter le flux dans plusieurs threads, et dans de telles situations, l'ordre d'exécution n'est pas défini. Java requiert uniquement la fin de tous les threads avant d'appeler toute opération de terminal, telle que Collectors.toList (). Regardons un exemple où nous appelons d'abord forEach () directement sur la collection, et deuxièmement, sur un flux parallèle:
Si nous exécutons le code plusieurs fois, nous voyons que list.forEach () traite les éléments dans l'ordre d'insertion, tandis que list.parallelStream (). ForEach () produit un résultat différent à chaque exécution. Une sortie possible est:
Un autre est:
la source