Quand utiliseriez-vous collect()
vs reduce()
? Quelqu'un a-t-il de bons exemples concrets de moments où il vaut vraiment mieux aller dans un sens ou dans l'autre?
Javadoc mentionne que collect () est une réduction mutable .
Étant donné qu'il s'agit d'une réduction modifiable, je suppose qu'elle nécessite une synchronisation (en interne) qui, à son tour, peut nuire aux performances. Il reduce()
est vraisemblablement plus facilement parallélisable au prix d'avoir à créer une nouvelle structure de données pour le retour après chaque étape de la réduction.
Les déclarations ci-dessus sont cependant des conjectures et j'aimerais qu'un expert intervienne ici.
java
java-8
java-stream
jimhooker2002
la source
la source
Réponses:
reduce
est une opération " fold ", elle applique un opérateur binaire à chaque élément du flux où le premier argument de l'opérateur est la valeur de retour de l'application précédente et le second argument est l'élément courant du flux.collect
est une opération d'agrégation où une "collection" est créée et chaque élément est "ajouté" à cette collection. Les collections dans différentes parties du flux sont ensuite ajoutées ensemble.Le document que vous avez lié donne la raison d'avoir deux approches différentes:
Le fait est donc que la parallélisation est la même dans les deux cas, mais dans le
reduce
cas où nous appliquons la fonction aux éléments de flux eux-mêmes. Dans lecollect
cas où nous appliquons la fonction à un conteneur mutable.la source
int
est immuable , vous ne pouvez donc pas utiliser facilement une opération de collecte. Vous pourriez faire un sale hack comme utiliser uneAtomicInteger
ou une coutume,IntWrapper
mais pourquoi le feriez-vous? Une opération de pliage est simplement différente d'une opération de collecte.reduce
méthode, où vous pouvez renvoyer des objets de type différent des éléments du flux.La raison en est simplement que:
collect()
ne peut fonctionner qu'avec des objets de résultat mutables .reduce()
est conçu pour fonctionner avec des objets de résultat immuables .reduce()
Exemple " avec immuable"collect()
Exemple " avec mutable"Par exemple, si vous souhaitez calculer manuellement une somme en utilisant,
collect()
il ne peut pas travailler avecBigDecimal
mais uniquement avecMutableInt
deorg.apache.commons.lang.mutable
par exemple. Voir:Cela fonctionne car l' accumulateur
container.add(employee.getSalary().intValue());
n'est pas censé renvoyer un nouvel objet avec le résultat mais changer l'état du mutablecontainer
de typeMutableInt
.Si vous souhaitez utiliser à la
BigDecimal
place pour le,container
vous ne pouvez pas utiliser lacollect()
méthode carcontainer.add(employee.getSalary());
cela ne changerait pas lecontainer
carBigDecimal
il est immuable. (A part celaBigDecimal::new
ne fonctionnerait pas carBigDecimal
il n'y a pas de constructeur vide)la source
Integer
constructeur (new Integer(6)
), qui est obsolète dans les versions ultérieures de Java.Integer.valueOf(6)
StringBuilder
qui est mutable. Voir: hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…La réduction normale est censée combiner deux valeurs immuables telles que int, double, etc. et en produire une nouvelle; c'est une réduction immuable . En revanche, la méthode collect est conçue pour muter un conteneur pour accumuler le résultat qu'il est censé produire.
Pour illustrer le problème, supposons que vous souhaitiez réaliser en
Collectors.toList()
utilisant une simple réduction commeC'est l'équivalent de
Collectors.toList()
. Cependant, dans ce cas, vous modifiez le fichierList<Integer>
. Comme nous le savons, leArrayList
n'est pas thread-safe, et il n'est pas sûr d'ajouter / supprimer des valeurs pendant l'itération, de sorte que vous obtiendrez soit une exception simultanée,ArrayIndexOutOfBoundsException
soit tout type d'exception (en particulier lorsqu'il est exécuté en parallèle) lorsque vous mettez à jour la liste ou le combineur essaie de fusionner les listes parce que vous faites muter la liste en y accumulant (en ajoutant) les nombres entiers. Si vous voulez rendre ce thread-safe, vous devez passer une nouvelle liste à chaque fois, ce qui nuirait aux performances.En revanche, les
Collectors.toList()
travaux de la même manière. Cependant, il garantit la sécurité des threads lorsque vous accumulez les valeurs dans la liste. De la documentation de lacollect
méthode :Donc, pour répondre à votre question:
si vous avez des valeurs immuables telles que
ints
,doubles
,Strings
puis la réduction normale fonctionne très bien. Cependant, si vous avezreduce
vos valeurs dans disons uneList
(structure de données mutable), vous devez utiliser la réduction mutable avec lacollect
méthode.la source
x
threads, chacun "ajoutant à l'identité" puis se combinant. Bon exemple.public static void main(String[] args) { List<Integer> l = new ArrayList<>(); l.add(1); l.add(10); l.add(3); l.add(-3); l.add(-4); List<Integer> numbers = l.stream().reduce( new ArrayList<Integer>(), (List<Integer> l2, Integer e) -> { l2.add(e); return l2; }, (List<Integer> l1, List<Integer> l2) -> { l1.addAll(l2); return l1; });for(Integer i:numbers)System.out.println(i); } }
J'ai essayé et je n'ai pas eu d'exceptionSoit le flux a <- b <- c <- d
En réduction,
vous aurez ((a # b) # c) # d
où # est cette opération intéressante que vous aimeriez faire.
En collection,
votre collectionneur aura une sorte de structure de collecte K.
K consomme un. K consomme alors b. K consomme alors c. K consomme alors d.
À la fin, vous demandez à K quel est le résultat final.
K vous le donne ensuite.
la source
Ils sont très différents dans l'empreinte mémoire potentielle pendant l'exécution. Alors que
collect()
collecte et place toutes les données dans la collection,reduce()
vous demande explicitement de spécifier comment réduire les données qui ont traversé le flux.Par exemple, si vous souhaitez lire certaines données d'un fichier, les traiter et les placer dans une base de données, vous pouvez vous retrouver avec un code de flux Java similaire à celui-ci:
Dans ce cas, nous utilisons
collect()
pour forcer java à diffuser des données et à enregistrer le résultat dans la base de données. Sanscollect()
les données ne sont jamais lues et jamais stockées.Ce code génère heureusement une
java.lang.OutOfMemoryError: Java heap space
erreur d'exécution, si la taille du fichier est suffisamment grande ou si la taille du tas est suffisamment faible. La raison évidente est qu'il essaie d'empiler toutes les données qui ont traversé le flux (et, en fait, ont déjà été stockées dans la base de données) dans la collection résultante, ce qui fait exploser le tas.Cependant, si vous remplacez
collect()
parreduce()
- ce ne sera plus un problème car ce dernier réduira et supprimera toutes les données qui l'ont traversé.Dans l'exemple présenté, remplacez simplement
collect()
par quelque chose parreduce
:Vous n'avez même pas besoin de vous soucier de faire dépendre le calcul du
result
car Java n'est pas un pur langage FP (programmation fonctionnelle) et ne peut pas optimiser les données qui ne sont pas utilisées au bas du flux en raison des effets secondaires possibles. .la source
System.out.println (somme);
La fonction de réduction gère deux paramètres, le premier paramètre est la valeur de retour précédente dans le flux, le deuxième paramètre est la valeur de calcul actuelle dans le flux, il additionne la première valeur et la valeur actuelle en tant que première valeur dans le calcul suivant.
la source
Selon la documentation
Donc, fondamentalement, vous ne l'utiliseriez
reducing()
que lorsque vous êtes forcé dans une collecte. Voici un autre exemple :D'après ce tutoriel, réduire est parfois moins efficace
L'identité est donc «réutilisée» dans un scénario de réduction, donc légèrement plus efficace à utiliser
.reduce
si possible.la source