Dans Java8 Stream Rough Summary, j'ai présenté la plupart des opérations Stream, mais j'écrirai sur les opérations de réduction car il semble qu'une explication détaillée soit nécessaire. Fondamentalement, je me réfère à JavaDoc of Stream.
Une opération de réduction est une opération qui renvoie le résultat de la combinaison de tous les éléments d'un flux en un seul à l'aide d'une fonction cumulative. Il semble que la réduction s'appelle convolution en japonais, mais ce n'est pas une soi-disant convolution. Cela ressemble plus à une puissance totale ou totale (ou plutôt, une puissance totale ou totale est un type de réduction). Les opérations de réduction incluent des réductions qui renvoient une valeur unique et des réductions variables qui renvoient un conteneur contenant plusieurs valeurs (comme une collection).
La méthode reduction
renvoie le résultat de l'accumulation de chaque élément en utilisant une fonction cumulative (ʻaccumulator). Il existe trois types de remplacements dans la méthode
reduction`:
Méthode |
---|
T reduce(T identity, BinaryOperator<T> accumulator) |
Optional<T> reduce(BinaryOperator<T> accumulator) |
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) |
T: Type d'élément dans Stream
ʻAccumulatorest une fonction (interface) pour calculer le résultat cumulatif en répétant la somme des éléments. Dans la méthode
reduction, ʻaccumulator.apply
est appelé en interne pour additionner les deux valeurs afin de générer un résultat intermédiaire, et le processus de totalisation est répété pour le résultat intermédiaire afin d'obtenir le résultat final. ..
Par conséquent, ʻaccumulator.apply` doit être une opération pour laquelle la loi de combinaison est valable.
ʻIndetify est l'élément unitaire de ʻaccumulator.apply
.
Définition mathématique de l'élément unitaire
Élément arbitraire a dans l'ensemble et opérations binomiales dessus*E qui satisfait les propriétés suivantes est appelé un élément unitaire.
a * e = e * a = a
Expression de type Java
Un identifiant qui satisfait les propriétés suivantes pour tout élément a du flux est appelé un élément unité.
accumulator.apply(identify, a) == accumulator.apply(a, identify) == a
Pour donner un exemple concret, l'élément unité de + (additionnel) sur l'ensemble de nombres réels est 0, et l'élément unité de * (multiplication) est 1.
Quand a = 2
0 + 2 = 2 + 0 = 2
1 * 2 = 2 * 1 = 2
Si aucun élément du flux n'existe, la valeur d'origine est renvoyée suite à la réduction. Si vous ne spécifiez pas d'élément unit (la deuxième méthode de substitution), vous devez le spécifier autant que possible car cela simplifie le processus à l'intérieur de `reduction '(en particulier pour le traitement parallèle).
combiner
est une fonction (interface) pour combiner des résultats cumulatifs.
Dans les flux parallèles, combinez les résultats exécutés dans chaque thread.
Si vous ne spécifiez pas combiner
(première et deuxième méthodes de remplacement), ce processus de jointure est effectué par ʻaccumulator. De plus, pour les flux séquentiels, même si vous spécifiez
combiner`, il ne sera pas utilisé.
combiner
est le même que ʻaccumulator, l'élément unité doit être ʻidentity
, et ce doit être une fonction qui satisfait la loi de combinaison, et il doit satisfaire ce qui suit (compatible avec ʻaccumulator`).
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
Le cas où «combineur» est requis est lorsque les fonctions à exécuter lors de la sommation des éléments et lors de la combinaison des résultats cumulatifs sont différentes. Par exemple, la somme des carrés est le cas. Dans le cas de la somme des carrés, c'est $ a ^ 2 + b ^ 2 $ entre les éléments, mais si vous utilisez la même fonction lors de la combinaison des résultats, $ (a ^ 2 + b ^ 2) ^ 2 + ( Ce sera c ^ 2 + d ^ 2) ^ 2 $. Par conséquent, la réduction de la somme des carrés peut être réalisée en faisant du "combineur" une simple somme.
Stream.iterate(1, x->x+1)
.limit(4)
.parallel()
.reduce(0, (x,y)->x*x + y*y, (x,y)->x+y);
Ce processus prend la somme des carrés de 1 à 4, donc le résultat est 30. Cependant, si vous n'utilisez pas «combiner», le résultat sera différent. Comme il est difficile de vérifier l'ordre de calcul dans un traitement parallèle, si vous passez au traitement séquentiel et exécutez ce traitement, le résultat sera 1172. C'est le résultat des calculs suivants:
(((((0^2+1^2)^2)+2^2)^2+3^2)^2+4^2) = 1172
Cependant, cette forme de traitement peut être simplement exprimée en utilisant «map».
Pour la somme des carrés, tout ce que vous avez à faire est de trouver le carré de chaque élément avec map
puis de le réduire.
Stream.iterate(1, x->x+1)
.limit(4)
.parallel()
.map(x->x*x)
.reduce(0, (x,y)->x*x + y*y);
La méthode collect
renvoie un conteneur de résultats mutable au lieu d'une valeur comme résultat.
Il existe deux types de remplacements pour la méthode collect
.
Méthode |
---|
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) |
<R,A> R collect(Collector<? super T,A,R> collector) |
La première méthode consiste à définir vous-même chaque opération par le conteneur de résultats. Si vous passez respectivement les opérations de création, d'ajout et de jointure, chaque opération sera utilisée pour la réduction.
fournisseur
est le processus de création d'une instance du conteneur de résultats.
En gros, vous passerez souvent la result container class :: new
, mais si vous avez une classe Factory, vous pouvez aussi passer sa méthode de création.
Pour le traitement parallèle, le fournisseur est appelé plusieurs fois, mais à chaque fois, une nouvelle instance doit être créée.
ʻAccumulator est le processus pour ajouter un élément au conteneur de résultats. Pour les objets de type Collection, la méthode ʻadd
est applicable.
Ce doit être un processus qui détient la loi de la connexion.
«combiner» est le processus de combinaison de deux conteneurs de résultats. Pour le traitement parallèle, les méthodes de résultat créées dans chaque thread sont combinées par «combineur». Pour les objets de type Collection, la méthode ʻaddAll` est applicable. Comme pour le traitement de réduction normal, la loi de couplage doit tenir et le traitement doit être compatible avec "accumulateur". De plus, il n'est pas utilisé pour les flux séquentiels.
Par exemple, le processus de réduction qui stocke les éléments dans le flux dans ArrayList et les renvoie est le suivant.
Stream.iterate(1, x->x+1)
.limit(10)
.parallel()
.collect(ArrayList::new,
Collection::add,
Collection::addAll));
Collector est un objet qui encapsule les opérations «fournisseur», «accumulateur» et «combineur». Le collecteur lui-même est une interface, et en plus des trois opérations ci-dessus, la méthode «caractéristiques» qui renvoie l'ensemble des caractéristiques de la collection et la méthode «finisseur» qui exécute le processus de conversion final sont définies. Vous pouvez créer votre propre objet Collector, mais la classe Collctors possède de nombreuses méthodes statiques qui renvoient des Collecteurs utiles.
Par exemple, le processus de création d'une liste comme précédemment peut être décrit comme suit en utilisant la méthode toList
.
Stream.iterate(1, x->x+1)
.limit(10)
.parallel()
.collect(Collectors.toList()));
Cependant, toList
ne garantit pas le type de liste retourné (bien que ce soit ArrayList lorsque je l'ai essayé dans le processus ci-dessus).
Si vous voulez décider du type de Collection à utiliser plus en détail, il existe une méthode toCollection
.
Stream.iterate(1, x->x+1)
.limit(10)
.parallel()
.collect(Collectors.toCollection(ArrayList::new)));
Dans la méthode toCollection
, seul le fournisseur
(processus de génération) est passé.
N'importe quelle classe d'implémentation de Collection peut être utilisée, donc il peut s'agir de HashSet (bien qu'il existe également une méthode appelée toSet
pour Set).
Il existe de nombreuses méthodes pratiques comme celle-ci dans la classe Collectors, vous pouvez donc facilement utiliser la réduction de variable en les utilisant.
Recommended Posts