View
302
Download
0
Category
Preview:
Citation preview
#DevoxxFR
L’API Collectordans tous ses états@JosePaumard
https://github.com/JosePaumard
https://www.slideshare.net/jpaumard
https://www.youtube.com/user/JPaumard
#DevoxxFR #ColJ8
@JosePaumard
Microsoft Virtual Academy
#DevoxxFR #ColJ8
@JosePaumard
#DevoxxFR #ColJ8
Questions ?#ColJ8
#DevoxxFR #ColJ8
Collectors ?Pourquoi s’intéresser aux collectors ?
▪ Partie intégrante de l’API Stream
▪ En général un peu laissée de côté
#DevoxxFR #ColJ8
Collectors ?YouTube :
▪ Tutoriaux sur les Streams ~670k
▪ Tutoriaux sur les Collectors < 4,5k
#DevoxxFR #ColJ8
Collectors ?Pourquoi s’intéresser aux collectors ?
▪ Partie intégrante de l’API Stream
▪ En général un peu laissée de côté
▪ Bien comprise elle peut simplifier les traitements
#DevoxxFR #ColJ8
movies.stream().flatMap(movie -> movie.actors().stream()).collect(
Collectors.groupingBy(Function.identity(), Collectors.counting()
)).entrySet().stream().max(Map.Entry.comparingByValue()).get();
#DevoxxFR #ColJ8
movies.stream().collect(
Collectors.groupingBy(movie -> movie.releaseYear(),
Collector.of(() -> new HashMap<Actor, AtomicLong>(), (map, movie) -> {
movie.actors().forEach(actor -> map.computeIfAbsent(actor, a -> new AtomicLong()).incrementAndGet()
) ;},(map1, map2) -> {
map2.entrySet().stream().forEach(entry -> map1.computeIfAbsent(entry.getKey(), a -> new AtomicLong()).addAndGet(entry.getValue().get())
) ;return map1 ;
}, new Collector.Characteristics [] {
Collector.Characteristics.CONCURRENT.CONCURRENT}
))
).entrySet().stream().collect(
Collectors.toMap(entry5 -> entry5.getKey(),entry5 -> entry5.getValue()
.entrySet().stream()
.max(Map.Entry.comparingByValue(Comparator.comparing(l -> l.get())))
.get())
.entrySet()
.stream()
.max(Comparator.comparing(entry -> entry.getValue().getValue().get()))
.get();
#DevoxxFR #ColJ8
Collectors ?Pourquoi s’intéresser aux collectors ?
▪ Partie intégrante de l’API Stream
▪ En général un peu laissée de côté
▪ Bien comprise elle peut simplifier les traitements
▪ Avec quelques précautions…
#DevoxxFR #ColJ8
Quelques mots sur les Stream
#DevoxxFR #ColJ8
Dualité dans les StreamsDans les streams :
▪ Objet qui se connecte à une source
▪ Opérations intermédiaires / terminales
▪ Certaines opérations terminales peuvent être des collectors
▪ Ces collectors peuvent prendre d’autres collectors en paramètres…
#DevoxxFR #ColJ8
Dualité dans les StreamsDans les streams :
▪ Toute opération sur un stream peut être modélisée par un collector
▪ Quel intérêt ?
stream.collect(collector);
#DevoxxFR #ColJ8
Ou va-t-on ? « Petit » rappel sur les streams
Les collectors qui existent
Comment étendre les collectors qui existent
Comment rendre le code lisible
Les collectors qui n’existent pas
#DevoxxFR #ColJ8
Opérations intermédiaires
#DevoxxFR #ColJ8
Un Stream c’est…Un objet qui se connecte à une source de données et les regarde passer
Un stream ne « contient pas » de données
stream
#DevoxxFR #ColJ8
stream
Change le type des données = mapping
#DevoxxFR #ColJ8
stream
Ne laisse pas tout passer = filtrage
#DevoxxFR #ColJ8
Mise à plat = flatMap
stream
#DevoxxFR #ColJ8
Mise à plat = flatMap
stream
#DevoxxFR #ColJ8
Map, Filter, FlatMapTrois opérations qui ne stockent aucune information pour fonctionner
Un objet entre = un objet sort, sans délai
Pas de le cas de toutes les opérations…
#DevoxxFR #ColJ8
Tri en fonction d’un comparateur…Nécessite de « voir » toutes les données
stream
#DevoxxFR #ColJ8
stream
DistinctLes données peuvent traverser une par
une, certaines sont éliminées
#DevoxxFR #ColJ8
Distinct, sortedSorted :
a besoin de « tout voir » pour faire le tri
Distinct :
doit « retenir ce qui passe » et laisse passer
Dans les deux cas : besoin d’un buffer
#DevoxxFR #ColJ8
Opérations intermédiaires2 catégories :
- Opérations directes = stateless
- Opérations avec buffer = stateful
#DevoxxFR #ColJ8
Cas de limit et skipDeux méthodes qui dépendent de l’ordre des éléments :
- Limit = conserve les n premiers éléments
- Skip = saute les n premiers éléments
Ne gère pas un buffer mais un compteur
Besoin de voir les données « dans l’ordre »
#DevoxxFR #ColJ8
Opérations terminales
#DevoxxFR #ColJ8
Intermédiaire vs terminaleSeule une opération terminale déclenche la consommation des éléments de la source
#DevoxxFR #ColJ8
Opérations terminalesPremier paquet :
- forEach
- count
- max, min
- reduce
- toArray
#DevoxxFR #ColJ8
Opérations terminalesPremier paquet :
- forEach
- count
- max, min
- reduce
- toArray
Consomment toutes les données
#DevoxxFR #ColJ8
Opérations terminalesDeuxième paquet :
- allMatch
- anyMatch
- noneMatch
- findFirst
- findAny
#DevoxxFR #ColJ8
Opérations terminalesDeuxième paquet :
- allMatch
- anyMatch
- noneMatch
- findFirst
- findAny
N’ont pas besoin de consommer toutes les données
#DevoxxFR #ColJ8
Opérations terminalesCas particuliers :
- max
- min
- reduce
Retourne un optional (cas des streams vides)
https://www.youtube.com/watch?v=Ej0sss6cq14
#DevoxxFR #ColJ8
Un premier collectorEt puis il y a collect !
Probablement le plus utilisé :
Prend un collector en paramètre
List<String> result = strings.stream()
.filter(s -> s.itEmpty())
.collect(Collectors.toList());
#DevoxxFR #ColJ8
Un premier collector (bis)Et puis il y a collect !
Probablement le plus utilisé :
Prend un collector en paramètre
Set<String> result = strings.stream()
.filter(s -> s.itEmpty())
.collect(Collectors.toSet());
#DevoxxFR #ColJ8
Un deuxième collectorEt puis il y a collect !
Peut-être moins connu ?
Prend un collector en paramètre
String authors = authors.stream()
.map(Author::getName)
.collect(Collectors.joining(", "));
Demo Time
#DevoxxFR #ColJ8
Un troisième collectorConstruction de Map
Map<Integer, List<String>> result = strings.stream()
.filter(s -> s.itEmpty())
.collect(Collectors.groupingBy(
s -> s.length())
);
#DevoxxFR #ColJ8
3
4
5
one, two, three, four, five, six, seven, eight, nine, ten
one, two, six, ten
four, five, nine
three, seven, eight
groupingBy(String::length)
Map<Integer, List<String>>
#DevoxxFR #ColJ8
3
4
5
one, two, three, four, five, six, seven, eight, nine, ten
one, two, six, ten
four, five, nine
three, seven, eight
groupingBy(String::length, downstream)
.stream().collect(downstream)
.stream().collect(downstream)
.stream().collect(downstream)
#DevoxxFR #ColJ8
3
4
5
one, two, three, four, five, six, seven, eight, nine, ten
one, two, six, ten
four, five, nine
three, seven, eight
groupingBy(String::length, Collectors.counting())
4L
3L
3L
Map<Integer, Long>
#DevoxxFR #ColJ8
Un troisième collector (bis)Construction de Map
Map<Integer, Long> result = strings.stream()
.filter(s -> s.itEmpty())
.collect(Collectors.groupingBy(
s -> s.length(), Collectors.counting())
);
Demo Time
#DevoxxFR #ColJ8
Un collector qui compteNombre d’articles par auteur
#DevoxxFR #ColJ8
Gent & Walsh, Beyond NP: The QSAT Phase TransitionGent & Hoos & Prosser & Walsh, Morphing: Combining…
A1 A2
Gent
Walsh
Gent
Hoos
Prosser
Walsh
flatMap(Article::getAuthors)
#DevoxxFR #ColJ8
Gent & Walsh, Beyond NP: The QSAT Phase TransitionGent & Hoos & Prosser & Walsh, Morphing: Combining…
Gent, Walsh, Gent, Hoos, Prosser, Walsh
flatMap(Article::getAuthors)
Gent
Walsh
Hoos
2L
2L
1L
Prosser 1L
groupingBy(
)
groupingBy(identity(),counting()
)
groupingBy(identity(),
)
Demo Time
#DevoxxFR #ColJ8
Supply, accumulate and combine
#DevoxxFR #ColJ8
Construction de listesReprenons le code :
List<String> result = strings.stream()
.filter(s -> s.itEmpty())
.collect(Collectors.toList());
#DevoxxFR #ColJ8
stream a b b
collector1) construire la liste2) ajouter un élément
a b c
ArrayList
#DevoxxFR #ColJ8
Construction de listes1) Construction de la liste : supplier
2) Ajout d’un élément à la liste : accumulateur
Supplier<List> supplier = () -> new ArrayList();
BiConsumer<List<E>, E> accumulator = (list, e) -> list.add(e);
#DevoxxFR #ColJ8
Cas parallèle
Stream
Collector
collector1) construire la liste2) ajouter un élément3) fusionner
CPU 2
Stream
CollectorCPU 1
#DevoxxFR #ColJ8
Construction de listes1) Construction de la liste : supplier
2) Ajout d’un élément à la liste : accumulateur
3) Combinaison de deux listes
Supplier<List> supplier = ArrayList::new;
BiConsumer<List<E>, E> accumulator = List::add;
BiConsumer<List<E>, List<E>> combiner = List::addAll;
#DevoxxFR #ColJ8
Construction de listesCe qui donne :
List<String> result = strings.stream()
.filter(s -> s.itEmpty())
.collect(ArrayList::new,List::add, List::adAll);
#DevoxxFR #ColJ8
Construction de listesCe qui donne :
List<String> result = strings.stream()
.filter(s -> s.itEmpty())
.collect(ArrayList::new,Collection::add, Collection::adAll);
#DevoxxFR #ColJ8
Construction de setsCe qui donne :
Set<String> result = strings.stream()
.filter(s -> s.itEmpty())
.collect(HashSet::new,Collection::add, Collection::adAll);
#DevoxxFR #ColJ8
Concaténation de StringPlutôt qu’une liste on veut construire le résultat dans une chaîne de caractères séparée par des virgules :
« one, two, six »
Ne fonctionne que sur les streams de String
#DevoxxFR #ColJ8
Concaténation de StringMéthode collect :
strings.stream().filter(s -> s.length() == 3).collect(() -> new String(),
(finalString, s) -> finalString.concat(s), (s1, s2) -> s1.concat(s2));
#DevoxxFR #ColJ8
Concaténation de StringMéthode collect :
strings.stream().filter(s -> s.length() == 3).collect(() -> new String(),
(finalString, s) -> finalString.concat(s), (s1, s2) -> s1.concat(s2));
#DevoxxFR #ColJ8
Concaténation de StringMéthode collect :
strings.stream().filter(s -> s.length() == 3).collect(() -> new StringBuilder(),
(sb, s) -> sb.append(s), (sb1, sb2) -> sb1.append(sb2));
#DevoxxFR #ColJ8
Concaténation de StringMéthode collect :
strings.stream().filter(s -> s.length() == 3).collect(StringBuilder::new,
StringBuilder::append, StringBuilder::append);
#DevoxxFR #ColJ8
Concaténation de StringMéthode collect :
StringBuilder stringBuilder = strings.stream()
.filter(s -> s.length() == 3)
.collect(StringBuilder::new,StringBuilder::append, StringBuilder::append);
#DevoxxFR #ColJ8
Concaténation de StringMéthode collect :
String string = strings.stream()
.filter(s -> s.length() == 3)
.collect(StringBuilder::new,StringBuilder::append, StringBuilder::append)
.toString();
#DevoxxFR #ColJ8
Un collector3 opérations
- supplier, construit un container de calcul
- accumulator
- combiner
#DevoxxFR #ColJ8
Un collector3 opérations + 1
- supplier, construit un container de calcul
- accumulator
- combiner
- finisher, qui peut être la fonction identité
Demo Time
#DevoxxFR
Collectors custom :1) Mapping2) Filtrage, flat map3) Jointures
Coffee break!
#DevoxxFR #ColJ8
À propos des types
#DevoxxFR #ColJ8
Interface Collectorpublic interface Collector<T, A, R> {
public Supplier<A> supplier(); // A : container intermédiaire
public BiConsumer<A, T> accumulator();// T : éléments traités
public BinaryOperator<A> combiner(); // retourne un A
public Function<A, R> finisher(); // touche finale
}
#DevoxxFR #ColJ8
Interface Collectorpublic interface Collector<T, A, R> {
public Supplier<A> supplier(); // A : container intermédiaire
public BiConsumer<A, T> accumulator();// T : éléments traités
public BinaryOperator<A> combiner(); // retourne un A
public Function<A, R> finisher(); // touche finale
public Set<Characteristics> characteristics();}
#DevoxxFR #ColJ8
Type d’un collectorDécodage
- T : type des éléments du stream
- A : type du container intermédiaire
- R : type du container final
On a souvent A = R
Le finisher est souvent la fonction identité≠
#DevoxxFR #ColJ8
3
4
5
one, two, three, four, five, six, seven, eight, nine, ten
one, two, six, ten
four, five, nine
three, seven, eight
groupingBy(String::length)
#DevoxxFR #ColJ8
3
4
5
one, two, three, four, five, six, seven, eight, nine, ten
one, two, six, ten
four, five, nine
three, seven, eight
Collector<String, ?, Map<Integer, List<String>> > c = groupingBy(String::length)
#DevoxxFR #ColJ8
3
4
5
one, two, three, four, five, six, seven, eight, nine, ten
one, two, six, ten
four, five, nine
three, seven, eight
Collector<String, ?, Map<Integer, List<String>> > c = groupingBy(String::length)
#DevoxxFR #ColJ8
3
4
5
one, two, three, four, five, six, seven, eight, nine, ten
one, two, six, ten
four, five, nine
three, seven, eight
Collector<String, ?, Map<Integer, List<String>> > c = groupingBy(String::length)
#DevoxxFR #ColJ8
one, two, three, four, five, six, seven, eight, nine, ten
Collector<String, ?, Map<Integer, List<String>> > c = groupingBy(
String::length,?
)
#DevoxxFR #ColJ8
one, two, three, four, five, six, seven, eight, nine, ten
Collector<String, ?, Map<Integer, List<String>> > c = groupingBy(
String::length,Collector<String, ?, >
)
#DevoxxFR #ColJ8
one, two, three, four, five, six, seven, eight, nine, ten
Collector<String, ?, Map<Integer, Value>> c = groupingBy(
String::length,Collector<String, ?, Value>
)
counting() : Collector<T, ?, Long>
#DevoxxFR #ColJ8
Opérations intermédiaires
#DevoxxFR #ColJ8
7634L {2004, 7634L}
Map<Long, List<Entry<Integer, Long>>>
#DevoxxFR #ColJ8
7634L {2004, 7634L}
Map<Long, List<Entry<Integer, Long>>>
Entry<Integer, Long> -> Integer = mapping
Function<> mapper = entry -> entry.getKey();
Collectors.mapping(mapper, toList());
Demo Time
#DevoxxFR #ColJ8
Mapping et toMapUn collector pour faire du mapping
Prend un downstream obligatoire
toMap :
stream.collect(mapping(function, downstream));
stream.collect(toMapping(
t -> key, t -> value));
#DevoxxFR #ColJ8
Collector intermédiairesLe collector mapping intègre une opération intermédiairestream.collect(mapping(function, downstream));
#DevoxxFR #ColJ8
Collector intermédiairesLe collector mapping intègre une opération intermédiaire
Intérêt ?
Pouvoir créer des downstream collectors
Un collector = intégrer le traitement d’un stream en totalité
#DevoxxFR #ColJ8
Collector intermédiairesSi l’on peut faire du mapping, pourquoi ne pas faire du filtrage ou du flatmap ?
… on aura ces deux collectors dans Java 9
#DevoxxFR #ColJ8
Interface Collectorpublic interface Collector<T, A, R> {
public Supplier<A> supplier(); // A : container intermédiaire
public BiConsumer<A, T> accumulator();// T : éléments traités
public BinaryOperator<A> combiner(); // retourne un A
public Function<A, R> finisher(); // touche finale
public Set<Characteristics> characteristics();}
#DevoxxFR #ColJ8
Collector intermédiairesLe collector mapping intègre une opération intermédiairestream.collect(mapping(function, downstream));
stream.collect(filtering(predicate, downstream));
Stream<T> donc Predicate<T>Et Collector<T, ?, R>
#DevoxxFR #ColJ8
Collector intermédiairesLe collector mapping intègre une opération intermédiaire
Stream<T> donc Function<T, Stream<TT>>
Et Collector<TT, ?, R>
stream.collect(mapping(function, downstream));
stream.collect(flatMapping(flatMapper, downstream));
Demo Time
#DevoxxFR #ColJ8
Caractéristiques Trois caractéristiques pour les collectors:
- IDENTITY_FINISH : le finisher est la fonction identité
- UNORDERED : le collector ne conserve pas l’ordre des éléments
- CONCURRENT : il est thread safe
#DevoxxFR #ColJ8
Application 1) L’auteur qui a le plus publié
2) L’auteur qui a le plus publié en un an
Demo Time
#DevoxxFR #ColJ8
Application L’intérêt est de modéliser un traitement dans un collecteur :
Pouvoir utiliser le collector en tant que downstream pour des traitements plus avancés
#DevoxxFR #ColJ8
Autre application 1) Les deux auteurs qui ont le plus publié
ensemble
2) Les deux auteurs qui ont le plus publié ensemble en un an
Utilisation de StreamsUtils
#DevoxxFR #ColJ8
Gent & Walsh, Beyond NP: The QSAT Phase TransitionGent & Hoos & Prosser & Walsh, Morphing: Combining…
Gent, Hoos, Prosser, Walsh
Gent, Walsh
{Gent, Walsh}
{Gent, Hoos} {Gent, Prosser} {Gent, Walsh}{Hoos, Prosser} {Hoos, Walsh}{Prosser, Walsh}
flatMap()
Demo Time
#DevoxxFR #ColJ8
Problème ?On peut avoir un problème si le stream des paires d’auteurs est vide
Ce qui arrive si les seuls articles sont des articles à un auteur
Sur toute la base, pas de risque
… mais quand on regroupe par année…
#DevoxxFR #ColJ8
Inverser une relation
#DevoxxFR #ColJ8
Inversion d’une relationOn a travaillé sur la relation entre les articles et leurs auteurs
On veut inverser cette relation : connaître les articles par auteur
#DevoxxFR #ColJ8
Gent & Walsh, Beyond NP: The QSAT Phase Transition
{Art1, Gent}, {Art1, Walsh}
{Art1, {Gent, Walsh}}
flatMap()
groupingBy()
#DevoxxFR #ColJ8
Conclusion
#DevoxxFR #ColJ8
API CollectorAPI très riche
Un peu complexe, nécessite quelques repères
Surtout, nécessite de comprendre le traitement que l’on veut faire
#DevoxxFR #ColJ8
API CollectorUn collector = un objet qui modélise un traitement de données dans sa totalité
Peut donc être utilisé comme traitement partiel d’un traitement plus large
#DevoxxFR
Merci pour votre attention
#DevoxxFR
Questions ?
@JosePaumard
https://github.com/JosePaumard
https://www.slideshare.net/jpaumard
https://www.youtube.com/user/JPaumard
Recommended