web-dev-qa-db-fra.com

Comment fonctionne la méthode Reduce () dans Java 8?

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?

22
user9608350

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;
            });
20
Eugene

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

  • Identité - un élément qui est la valeur initiale de l'opération de réduction et le résultat par défaut si le flux est vide
  • itemAccumulator - une fonction qui prend deux paramètres: un résultat partiel de l'opération de réduction et l'élément suivant du flux
  • Combiner - une fonction qui prend deux paramètres: un résultat partiel de l'opération de réduction et l'élément suivant du combinateur de flux - ne fonction utilisée pour combiner le résultat partiel de l'opération de réduction lorsque le la réduction est parallélisée, ou lorsqu'il y a un décalage entre les types des arguments de l'accumulateur et les types de l'implémentation de l'accumulateur

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.

12
Deadpool

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:

  1. result = operationOn('a', 'b') - le operationOn peut être n'importe quoi (somme des longueurs des entrées ..)
  2. result = operationOn(result, 'c')
  3. result = operationOn(result, 'd')
  4. result is returned

Les méthodes sont les suivantes:

8
Nikolas

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.

2
glglgl