web-dev-qa-db-fra.com

Quelle est la différence entre Collection.stream (). ForEach () et Collection.forEach ()?

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);
260
VladS

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.forEachprend 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.

259
Stuart Marks

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)

21
Angelo Fuchs

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.

8
skiwi