L'API Collector dans tous ses états

Post on 21-Apr-2017

302 views 0 download

Transcript of L'API Collector dans tous ses états

#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