Comprendre Spliterator, Collector et Stream dans Java 8

143

J'ai du mal à comprendre l' Streaminterface de Java 8, en particulier lorsqu'il s'agit de SpliteratoretCollector interfaces . Mon problème est que je ne peux tout simplement pas comprendre Spliteratoret les Collectorinterfaces encore, et par conséquent, l' Streaminterface est encore quelque peu obscure pour moi.

Qu'est-ce qu'un Spliteratoret unCollector , et comment puis-je les utiliser? Si je suis prêt à écrire le mien Spliteratorou Collector(et probablement le mien Streamdans ce processus), que dois-je faire et ne pas faire?

J'ai lu quelques exemples éparpillés sur le Web, mais comme tout ici est encore nouveau et sujet à modifications, les exemples et les tutoriels sont encore très rares.

Victor Stafusa
la source

Réponses:

142

Vous ne devriez presque certainement jamais avoir à traiter en Spliteratortant qu'utilisateur; cela ne devrait être nécessaire que si vous écrivez Collectionvous-même des types et que vous avez également l' intention d'optimiser les opérations parallélisées sur eux.

Pour ce que ça vaut, un Spliterator est une façon d'opérer sur les éléments d'une collection de manière à ce qu'il soit facile de séparer une partie de la collection, par exemple parce que vous êtes en parallèle et que vous voulez qu'un thread fonctionne sur une partie de la collection, un fil pour travailler sur une autre pièce, etc.

Vous ne devriez pas non plus enregistrer de valeurs de type Streamdans une variable. Streamest un peu comme un Iterator, en ce sens qu'il s'agit d'un objet à usage unique que vous utiliserez presque toujours dans une chaîne fluide, comme dans l'exemple Javadoc:

int sum = widgets.stream()
                  .filter(w -> w.getColor() == RED)
                  .mapToInt(w -> w.getWeight())
                  .sum();

Collectorest la version la plus généralisée et la plus abstraite possible d'une opération «réduire» à la carte / réduire; en particulier, il doit prendre en charge les étapes de parallélisation et de finalisation. Des exemples de Collectors incluent:

  • sommation, par exemple Collectors.reducing(0, (x, y) -> x + y)
  • Ajout de StringBuilder, par exemple Collector.of(StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString)
Louis Wasserman
la source
31
Spliterator (s) fournit également un moyen de diffuser un Iterable qui n'est pas une collection
Bohemian
2
Je voulais dire "une opération de réduction, dans le sens où le terme est signifié en carte / réduction"
Louis Wasserman
1
Une Collectors.ofancienne méthode de version bêta a été supprimée ou est-ce que je manque quelque chose? Par souci d'exhaustivité, (x,y) -> x+ypeut s'écrire Integer::sum.
Jean-François Savard
3
Euh, non, désolé, c'est Collector.of, pas Collectors.of.
Louis Wasserman
2
Votre exemple de collectionneur serait plus utile si vous expliquiez ce que fait chacun de vos collectionneurs.
MiguelMunoz
90

Spliterator signifie essentiellement «itérateur fractionnable».

Un seul thread peut traverser / traiter tout le Spliterator lui-même, mais le Spliterator a également une méthode trySplit()qui "scindera" une section pour quelqu'un d'autre (généralement, un autre thread) à traiter - laissant le spliterator actuel avec moins de travail.

Collectorcombine la spécification d'une reducefonction (de renommée de réduction de la carte), avec une valeur initiale, et une fonction pour combiner deux résultats (permettant ainsi de combiner les résultats de flux de travail Spliterated).

Par exemple, le collecteur le plus basique aurait une valeur initiale de 0, ajouterait un entier à un résultat existant et «combinerait» deux résultats en les ajoutant. Additionnant ainsi un flux fractionné d'entiers.

Voir:

Thomas W
la source
une valeur pour combiner deux résultats?
Jason Law
@JasonLaw - clarifié! Merci pour la suggestion.
Thomas W
5

Voici des exemples d'utilisation des collecteurs prédéfinis pour effectuer des tâches de réduction mutable courantes:

 // Accumulate names into a List
 List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());

 // Accumulate names into a TreeSet
 Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));

 // Convert elements to strings and concatenate them, separated by commas
 String joined = things.stream()
                       .map(Object::toString)
                       .collect(Collectors.joining(", "));

 // Compute sum of salaries of employee
 int total = employees.stream()
                      .collect(Collectors.summingInt(Employee::getSalary)));

 // Group employees by department
 Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));

 // Compute sum of salaries by department
 Map<Department, Integer> totalByDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment,
                                               Collectors.summingInt(Employee::getSalary)));

 // Partition students into passing and failing
 Map<Boolean, List<Student>> passingFailing =
     students.stream()
             .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
Ajay Kumar
la source
2
Cela ne répond pas à la question de l'Op, et il n'y a aucune explication ou description de votre message.
Sid
4

Interface Spliterator- est une fonctionnalité principale de Streams .

Les méthodes stream()et parallelStream()par défaut sont présentées dans l' Collectioninterface. Ces méthodes utilisent le Spliterator via l'appel à spliterator():

...

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

...

Spliterator est un itérateur interne qui divise le flux en parties plus petites. Ces petites pièces peuvent être traitées en parallèle.

Parmi les autres méthodes, il y a deux plus importantes pour comprendre le Spliterator:

  • boolean tryAdvance(Consumer<? super T> action) Contrairement au Iterator, il essaie d'effectuer l'opération avec l'élément suivant. Si l'opération s'est exécutée avec succès, la méthode retourne true. Sinon, renvoie false- cela signifie qu'il y a absence d'élément ou de fin de flux.

  • Spliterator<T> trySplit() Cette méthode permet de diviser un ensemble de données en plusieurs ensembles plus petits selon l'un ou l'autre des critères (taille du fichier, nombre de lignes, etc.).


la source
«Si l’opération s’est exécutée avec succès…» Vous devriez probablement reformuler ceci. tryAdvance javadoc est plus clair: «Si un élément restant existe, exécute l'action donnée dessus, en retournant true; else renvoie false.´
Piro dit Réintégrer Monica le