Je comprends qu'avec .stream()
, je peux utiliser des opérations en chaîne comme .filter()
ou utiliser un flux parallèle. Mais quelle est la différence entre eux si je dois exécuter de petites opérations (par exemple, imprimer les éléments de la liste)?
collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
Pour des cas simples tels que celui illustré, ils sont généralement les mêmes. Cependant, il existe un certain nombre de différences subtiles qui pourraient être significatives.
Un problème est avec la commande. Avec Stream.forEach
, la commande est non définie . Il est peu probable que des flux séquentiels se produisent. Néanmoins, il est dans la spécification de Stream.forEach
de s'exécuter dans un ordre arbitraire. Cela se produit fréquemment dans des flux parallèles. Par contre, Iterable.forEach
est toujours exécuté dans l'ordre d'itération de la variable Iterable
, si spécifié.
Un autre problème concerne les effets secondaires. L'action spécifiée dans Stream.forEach
doit nécessairement ne pas interférer . (Voir le documentation du package Java.util.stream .) Iterable.forEach
a potentiellement moins de restrictions. Pour les collections dans Java.util
, Iterable.forEach
utilisera généralement le Iterator
de cette collection, dont la plupart sont conçus pour être fail-fast et qui lancera ConcurrentModificationException
si la collection est structurellement modifiée pendant l'itération. Cependant, les modifications non structurelles sont autorisées lors de l'itération. Par exemple, le documentation de la classe ArrayList indique "le simple fait de définir la valeur d'un élément n'est pas une modification structurelle". Ainsi, l'action pour ArrayList.forEach
est autorisée à définir des valeurs dans le ArrayList
sous-jacent sans problèmes.
Les collections simultanées sont encore différentes. Au lieu d'échec rapide, ils sont conçus pour être faiblement cohérent . La définition complète est à ce lien. Brièvement, cependant, considérons ConcurrentLinkedDeque
. L'action passée à sa méthode forEach
est autorisée à modifier le deque sous-jacent, même structurellement, et ConcurrentModificationException
n'est jamais levé. Cependant, la modification effectuée peut être visible ou non dans cette itération. (D'où la consistance "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 le verrou de la collection une fois et le conserve pendant tous les appels à la méthode d'action. L'appel Stream.forEach
utilise le séparateur de la collection, qui ne se verrouille pas et qui repose sur la règle de non-ingérence en vigueur. La collection sauvegardant le flux pourrait être modifiée pendant l'itération et, le cas échéant, un comportement ConcurrentModificationException
ou incohérent pourrait en résulter.
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és 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 coûteux. Pour les situations où vous bouclez très souvent, cela pourrait toujours être intéressant.
Vous devez répéter ces tests sous le système cible car il s’agit d’une implémentation spécifique, ( code source complet ).
J'exécute openjdk version 1.8.0_111 sur une machine Linux rapide.
J'ai écrit un test qui effectue une 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, d’autres considérations sont donc plus importantes dans pratiquement toutes les situations.
public int outside = 0;
private void forCounter(List<Integer> integers) {
for(int ii = 0; ii < integers.size(); ii++) {
Integer next = integers.get(ii);
outside = next*next;
}
}
private void forEach(List<Integer> integers) {
for(Integer next : integers) {
outside = next * next;
}
}
private void iteratorForEach(List<Integer> integers) {
integers.forEach((ii) -> {
outside = ii*ii;
});
}
private void iteratorStream(List<Integer> integers) {
integers.stream().forEach((ii) -> {
outside = ii*ii;
});
}
Voici mes timings: millisecondes/fonction/nombre d'entrées dans la liste. Chaque exécution correspond à 10 ^ 6 boucles.
1 10 100 1000 10000
for with index 39 112 920 8577 89212
iterator.forEach 27 116 959 8832 88958
for:each 53 171 1262 11164 111005
iterable.stream.forEach 255 324 1030 8519 88419
Si vous répétez l'expérience, j'ai posté le code source complet . Veuillez éditer cette réponse et ajouter vos résultats avec une notation du système testé.
I got:
1 10 100 1000 10000
iterator.forEach 27 106 1047 8516 88044
for:each 46 143 1182 10548 101925
iterable.stream.forEach 393 397 1108 8908 88361
for with index 49 145 887 7614 81130
(MacBook Pro, Intel Core i7 à 2,5 GHz, 16 Go, macOS 10.12.6)
Il n'y a pas de différence entre les deux que vous avez mentionnés, du moins sur le plan conceptuel, la Collection.forEach()
n'est qu'un raccourci.
En interne, la version stream()
a un peu plus de temps système en raison de la création d'objet, mais le temps d'exécution n'est pas non plus surchargé.
Les deux implémentations finissent par parcourir le contenu collection
une fois, et pendant l'itération imprime l'élément.