J'ai une liste myListToParse
où je veux filtrer les éléments et appliquer une méthode sur chaque élément, et ajouter le résultat dans une autre liste myFinalList
.
Avec Java 8, j'ai remarqué que je peux le faire de 2 manières différentes. J'aimerais connaître le moyen le plus efficace entre eux et comprendre pourquoi un moyen est meilleur que l'autre.
Je suis ouvert à toute suggestion concernant une troisième voie.
Méthode 1:
myFinalList = new ArrayList<>();
myListToParse.stream()
.filter(elt -> elt != null)
.forEach(elt -> myFinalList.add(doSomething(elt)));
Méthode 2:
myFinalList = myListToParse.stream()
.filter(elt -> elt != null)
.map(elt -> doSomething(elt))
.collect(Collectors.toList());
java
java-8
java-stream
Emilien Brigand
la source
la source
elt -> elt != null
peut être remplacé parObjects::nonNull
Optional<T>
place en combinaison avecflatMap
..map(this::doSomething)
supposant qu'ildoSomething
s'agit d'une méthode non statique. S'il est statique, vous pouvez le remplacerthis
par le nom de la classe.Réponses:
Ne vous inquiétez pas des différences de performances, elles seront normalement minimes dans ce cas.
La méthode 2 est préférable car
il ne nécessite pas de muter une collection qui existe en dehors de l'expression lambda,
il est plus lisible car les différentes étapes qui sont effectuées dans le pipeline de collecte sont écrites séquentiellement: d'abord une opération de filtrage, puis une opération de carte, puis la collecte du résultat (pour plus d'informations sur les avantages des pipelines de collecte, voir l' excellent article de Martin Fowler ),
vous pouvez facilement modifier la façon dont les valeurs sont collectées en remplaçant le
Collector
qui est utilisé. Dans certains cas, vous devrez peut-être écrire le vôtreCollector
, mais l'avantage est que vous pouvez facilement le réutiliser.la source
Je suis d'accord avec les réponses existantes que la deuxième forme est meilleure car elle n'a aucun effet secondaire et est plus facile à paralléliser (utilisez simplement un flux parallèle).
En termes de performances, il semble qu'ils soient équivalents jusqu'à ce que vous commenciez à utiliser des flux parallèles. Dans ce cas, la carte fonctionnera beaucoup mieux. Voir ci-dessous les résultats du micro benchmark :
Vous ne pouvez pas booster le premier exemple de la même manière car forEach est une méthode de terminal - elle renvoie void - vous êtes donc obligé d'utiliser un lambda avec état. Mais c'est vraiment une mauvaise idée si vous utilisez des flux parallèles .
Notez enfin que votre deuxième extrait de code peut être écrit d'une manière légèrement plus concise avec des références de méthode et des importations statiques:
la source
L'un des principaux avantages de l'utilisation de flux est qu'il permet de traiter les données de manière déclarative, c'est-à-dire en utilisant un style de programmation fonctionnel. Il offre également une capacité multi-threading gratuite, ce qui signifie qu'il n'est pas nécessaire d'écrire du code multi-thread supplémentaire pour rendre votre flux simultané.
En supposant que la raison pour laquelle vous explorez ce style de programmation est que vous souhaitez exploiter ces avantages, votre premier échantillon de code n'est potentiellement pas fonctionnel car la
foreach
méthode est classée comme étant terminale (ce qui signifie qu'elle peut produire des effets secondaires).La deuxième méthode est préférée du point de vue de la programmation fonctionnelle, car la fonction de carte peut accepter des fonctions lambda sans état. Plus explicitement, le lambda passé à la fonction map doit être
ArrayList
).Un autre avantage de la seconde approche est que si le flux est parallèle et que le collecteur est simultané et non ordonné, ces caractéristiques peuvent fournir des indications utiles à l'opération de réduction pour effectuer la collecte simultanément.
la source
Si vous utilisez des collections Eclipse, vous pouvez utiliser la
collectIf()
méthode.Il évalue avec empressement et devrait être un peu plus rapide que l'utilisation d'un Stream.
Remarque: je suis un committer pour les collections Eclipse.
la source
Je préfère la deuxième manière.
Lorsque vous utilisez la première méthode, si vous décidez d'utiliser un flux parallèle pour améliorer les performances, vous n'aurez aucun contrôle sur l'ordre dans lequel les éléments seront ajoutés à la liste de sortie par
forEach
.Lorsque vous utilisez
toList
, l'API Streams conservera l'ordre même si vous utilisez un flux parallèle.la source
forEachOrdered
au lieu deforEach
s'il voulait utiliser un flux parallèle tout en conservant l'ordre. Mais en tant que documentation desforEach
états, la préservation de l'ordre des rencontres sacrifie le bénéfice du parallélisme. Je soupçonne que c'est également le cas à l'toList
époque.Il existe une troisième option - en utilisant
stream().toArray()
- voir les commentaires sous pourquoi stream n'a pas de méthode toList . Il s'avère plus lent que forEach () ou collect (), et moins expressif. Il pourrait être optimisé dans les versions ultérieures de JDK, donc en l'ajoutant ici au cas où.en supposant
List<String>
avec un benchmark micro-micro, 1M d'entrées, 20% de nulls et une simple transformation dans doSomething ()
les résultats sont
parallèle:
séquentiel:
parallèle sans valeurs nulles et filtre (donc le flux est
SIZED
): toArrays a les meilleures performances dans ce cas, et.forEach()
échoue avec "indexOutOfBounds" sur le destinataire ArrayList, a dû remplacer par.forEachOrdered()
la source
Peut être la méthode 3.
Je préfère toujours garder la logique séparée.
la source
Si l'utilisation des bibliothèques 3rd Pary est correcte, cyclops- react définit les collections étendues Lazy avec cette fonctionnalité intégrée. Par exemple, nous pourrions simplement écrire
ListX myListToParse;
ListX myFinalList = myListToParse.filter (elt -> elt! = Null) .map (elt -> doSomething (elt));
myFinalList n'est pas évalué avant le premier accès (et après que la liste matérialisée soit mise en cache et réutilisée).
[Divulgation Je suis le principal développeur de cyclops-react]
la source