J'essaie de comprendre comment fonctionne la méthode reduce()
dans Java-8 .
Par exemple, j'ai ce code:
public class App {
public static void main(String[] args) {
String[] arr = {"lorem", "ipsum", "sit", "amet"};
List<String> strs = Arrays.asList(arr);
int ijk = strs.stream().reduce(0,
(a, b) -> {
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
},
(a, b) -> {
System.out.println("Combiner");
return a * b;
});
System.out.println(ijk);
}
}
Et la sortie est la suivante:
Accumulator, a = 0, b = lorem
Accumulator, a = 5, b = ipsum
Accumulator, a = 10, b = sit
Accumulator, a = 13, b = amet
17
C'est une somme de la longueur de ces chaînes. Et je vois que le combineur n'est pas accessible, donc il ne multipliera pas les nombres, il ajoutera seulement les nombres.
Mais si je remplace stream
par parallelStream
:
int ijk = strs.parallelStream().reduce(0,
(a, b) -> {
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
},
(a, b) -> {
System.out.println("Combiner");
return a * b;
});
System.out.println(ijk);
Voici la sortie:
Accumulator, a = 0, b = ipsum
Accumulator, a = 0, b = lorem
Accumulator, a = 0, b = sit
Combiner
Accumulator, a = 0, b = amet
Combiner
Combiner
300
Je vois que l'accumulateur et le combinateur sont accessibles tous les deux, mais seule la multiplication est de retour. Alors, que se passe-t-il avec la somme?
Vous devriez lire la documentation de reduce
qui dit:
De plus, la fonction combinateur doit être compatible avec la fonction accumulateur; pour tous les u et t, les éléments suivants doivent être respectés:
combiner.apply (u, accumulator.apply (identité, t)) == accumulator.apply (u, t)
Dans votre cas, vous enfreignez cette loi (faisant un sum dans accumulator
et multiplication dans le combiner
), donc le résultat que vous voir pour une telle opération est vraiment indéfini et dépend de la façon dont le Spliterator pour la source sous-jacente est implémenté (ne faites pas ça!).
De plus, le combiner
est seulement appelé pour un flux parallèle.
Bien sûr, toute votre approche pourrait être simplifiée pour:
Arrays.asList("lorem", "ipsum", "sit", "amet")
.stream()
.mapToInt(String::length)
.sum();
Si vous faites cela uniquement à des fins d'apprentissage, un reduce
correct serait (pour obtenir le sum
):
strs.parallelStream()
.reduce(0,
(a, b) -> {
System.out.println("Accumulator, a = " + a + ", b = " + b);
return a + b.length();
},
(a, b) -> {
System.out.println("Combiner");
return a + b;
});
Les concepts clés: identité, accumulateur et combinateur
opération Stream.reduce (): décomposons les éléments participants de l'opération en blocs séparés. De cette façon, nous comprendrons plus facilement le rôle que chacun joue
Lorsqu'un flux s'exécute en parallèle, le runtime Java divise le flux en plusieurs sous-flux. Dans de tels cas, nous devons utiliser une fonction pour combiner les résultats des sous-flux en un seul. Ceci est le rôle du combinateur
Cas 1: Le combinateur fonctionne avec parallelStream
comme indiqué dans votre exemple
Cas 2: Exemple d'accumulateur avec différents types d'arguments
Dans ce cas, nous avons un flux d'objets User et les types d'arguments de l'accumulateur sont Integer et User. Cependant, l'implémentation de l'accumulateur est une somme d'entiers, donc le compilateur ne peut tout simplement pas déduire le type du paramètre utilisateur.
List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());
Erreur de compilation
The method reduce(User, BinaryOperator<User>) in the type Stream<User> is not applicable for the arguments (int, (<no type> partialAgeResult, <no type> user) -> {})
Nous pouvons résoudre ce problème en utilisant un combinateur: qui est la référence de méthode Integer::sum
ou en utilisant l'expression lambda (a,b)->a+b
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(),Integer::sum);
Pour faire simple, si nous utilisons des flux séquentiels et les types d'arguments de l'accumulateur et les types de sa correspondance d'implémentation, nous n'avons pas besoin d'utiliser un combinateur.
Il existe 3 façons de réduire l'utilisation de Java-stream . En bref, Stream::reduce
Commence par deux éléments conséquents (ou une valeur d'identité un avec le premier) et effectue une opération avec eux produisant une nouvelle valeur réduite. Pour chaque élément suivant, la même chose se produit et une opération est effectuée avec la valeur réduite.
Supposons que vous ayez un flux de 'a'
, 'b'
, 'c'
Et 'd'
. La réduction effectue la séquence d'opérations suivante:
result = operationOn('a', 'b')
- le operationOn
peut être n'importe quoi (somme des longueurs des entrées ..)result = operationOn(result, 'c')
result = operationOn(result, 'd')
result is returned
Les méthodes sont les suivantes:
Optional<T> reduce(BinaryOperator<T> accumulator)
effectue une réduction sur les éléments. Commence par les deux premiers éléments produisant une valeur réduite, puis chaque élément avec la valeur réduite. Le Optional<T>
Est retourné car il n'est pas garanti que le flux d'entrée n'est pas vide.
T reduce(T identity, BinaryOperator<T> accumulator)
fait la même chose que la méthode ci-dessus, sauf que la valeur d'identité est donnée comme premier élément. Le T
est retourné car il y a toujours au moins un élément garanti, à cause de T identity
.
U reduce(U identity, BiFunction<U,? super T, U> accumulator, BinaryOperator<U> combiner)
fait la même chose que la méthode ci-dessus, avec un ajout que les fonctions sont combinées. Le U
est retourné car il y a toujours au moins un élément garanti, à cause de U identity
.
Je suppose que vous avez choisi de faire l'ajout et la multiplication comme une démo pour voir ce qui se passe exactement.
Comme vous l'avez déjà remarqué, et comme cela a déjà été mentionné, le combineur n'est appelé que sur des flux parallèles.
En bref, sur des strams parallèles, une partie du flux (resp. Le Spliterator sous-jacent) est coupée et traitée par un thread différent. Après le traitement de plusieurs pièces, leur résultat est combiné avec un combineur.
Dans votre cas, les quatre éléments sont tous traités par un thread différent et la combinaison se fait ensuite élément par élément. C'est pourquoi vous ne voyez aucun ajout (à part 0 +
) en cours d'application, mais uniquement la multiplication.
Pour obtenir un résultat significatif, vous devez cependant passer de *
à +
et faire une sortie plus significative.