L'API la plus utilisée du JDK est sans aucun doute l'API Collection. Brillamment conçue il y a un peu plus de 15 ans, elle est encore aujourd'hui au coeur de toutes les applications Java. En 2004, elle a subi son premier lifting, avec l'introduction des génériques. Cette mise à jour, bien qu'importante, n'a cependant pas modifié ses patterns d'utilisation. Avec l'introduction des lambdas en Java 8, l'API Collection est à nouveau réécrite, mais cette fois la situation est différente : ses patterns d'utilisation sont complètement changés.
La première partie de cette conférence introduit les lambda expressions, comment les écrire, et ce qu'elle nous apportent en tant que développeurs. La deuxième partir présente en détail les nouveaux patterns introduits par les API Stream et Collector. Ces nouvelles API vont changer la façon dont nous allons pouvoir traiter les collections de grande taille, y compris en parallèle, avec un modèle de programmation très simple, et des patterns très puissants. Cette puissance sera montrée dans des exemples réels, qui monteront comment Java 8 va pouvoir nous aider à écrire simplement du code efficace et performant.
12. Un exemple très simple
public class Person {
Un bon vieux bean
private String name ;
private int age ;
// constructors
// getters / setters
}
List<Person> list = new ArrayList<>() ;
… et une bonne vieille liste
13. Calculons la moyenne des âges des personnes
int sum = 0 ;
// I need a default value in case
// the list is empty
int average = 0 ;
for (Person person : list) {
sum += person.getAge() ;
}
if (!list.isEmpty()) {
average = sum / list.size() ;
}
14. Plus dur : pour les personnes de plus de 20 ans
int sum = 0 ;
int n = 0 ;
int average = 0 ;
for (Person person : list) {
if (person.getAge() > 20) {
n++ ;
sum += person.getAge() ;
}
}
if (n > 0) {
average = sum / n ;
}
15. Plus dur : pour les personnes de plus de 20 ans
int sum = 0 ;
int n = 0 ;
int average = 0 ;
for (Person person : list) {
if (person.getAge() > 20) {
n++ ;
sum += person.getAge() ;
}
}
if (n > 0) {
average = sum / n ;
}
« programmation impérative »
16. … pas une obligation !
select avg(age)
from Person
where age > 20
Ici on décrit le résultat
19. map
Person
age
age > 20
1ère étape : mapping
Mapping :
- prend une liste d’un type donné
- retourne une liste d’un autre type
- possède le même nombre d’éléments
sum
21. map
Person
filter
age
age > 20
2ème étape : filtrage
Filtrage :
- prend une liste d’un type donné
- retourne une liste du même type
- mais avec moins d’éléments
sum
25. La façon JDK 7
On crée une interface pour modéliser le mapper…
public interface Mapper<T, V> {
public V map(T t) ;
}
26. La façon JDK 7
… et on crée une classe anonyme
public interface Mapper<T, V> {
public V map(T t) ;
}
Mapper<Person, Integer> mapper = new Mapper<Person, Integer>() {
public Integer map(Person p) {
return p.getAge() ;
}
}
27. La façon JDK 7
On peut faire la même chose pour le filtrage
public interface Predicate<T> {
public boolean filter(T t) ;
}
28. La façon JDK 7
On peut faire la même chose pour le filtrage
public interface Predicate<T> {
public boolean filter(T t) ;
}
AgePredicate predicate = new Predicate<Integer>() {
public boolean filter(Integer i) {
return i > 20 ;
}
}
29. La façon JDK 7
Et enfin pour la réduction
public interface Reducer<T> {
public T reduce(T t1, T t2) ;
}
30. La façon JDK 7
Et enfin pour la réduction
public interface Reducer<T> {
public T reduce(T t1, T t2) ;
}
Reducer<Integer> reduction = new Reducer<Integer>() {
public Integer reduce(Integer i1, Integer i2) {
return i1 + i2 ;
}
}
31. La façon JDK 7
Au final, le pattern map / filter / reduce en JDK 7 :
1) Créer 3 interfaces
public interface Mapper<T, V> {
public V map(T t) ;
}
public interface Predicate<T> {
public boolean filter(T t) ;
}
public interface Reducer<T> {
public T reduce(T t1, T t2) ;
}
32. La façon JDK 7
Au final, le pattern map / filter / reduce en JDK 7 :
1) Créer 3 interfaces
2) Et on applique…
List<Person> persons = ... ;
int sum =
persons.map(
new Mapper<Person, Integer>() {
public Integer map(Person p) {
return p.getAge() ;
}
})
.filter(
new Filter<Integer>() {
public boolean filter(Integer age) {
return age > 20 ;
}
})
.reduce(0,
new Reducer<Integer>() {
public Integer recude(Integer i1, Integer i2) {
return i1 + i2 ;
}
}
}) ;
33. La façon JDK 7
Au final, le pattern map / filter / reduce en JDK 7 :
1) Créer 3 interfaces
2) Et on applique…
List<Person> persons = ... ;
int sum =
persons.map(
new Mapper<Person, Integer>() {
public Integer map(Person p) {
return p.getAge() ;
}
})
.filter(
new Filter<Integer>() {
public boolean filter(Integer age) {
return age > 20 ;
}
})
.reduce(0,
new Reducer<Integer>() {
public Integer recude(Integer i1, Integer i2) {
return i1 + i2 ;
}
}
}) ;
35. La façon du JDK 8
Prenons l’exemple du Mapper
mapper = new Mapper<Person, Integer>() {
public Integer map(Person person) {
return person.getAge() ;
}
}
36. La façon du JDK 8
Prenons l’exemple du Mapper
mapper = new Mapper<Person, Integer>() {
public Integer map(Person person) { // 1 méthode
return person.getAge() ;
}
}
37. La façon du JDK 8
Prenons l’exemple du Mapper
mapper = new Mapper<Person, Integer>() {
public Integer map(Person person) { // 1 méthode
return p.getAge() ;
On prend
}
}
person
mapper = (Person person)
;
38. La façon du JDK 8
Prenons l’exemple du Mapper
mapper = new Mapper<Person, Integer>() {
public Integer map(Person person) { // 1 méthode
return person.getAge() ;
}
}
et…
mapper = (Person person) ->
;
39. La façon du JDK 8
Prenons l’exemple du Mapper
mapper = new Mapper<Person, Integer>() {
public Integer map(Person person) { // 1 méthode
return person.getAge() ;
}
}
… on retourne
mapper = (Person person) -> person.getAge() ;
son âge
40. La façon du JDK 8
Prenons l’exemple du Mapper
mapper = new Mapper<Person, Integer>() {
public Integer map(Person person) { // 1 méthode
return person.getAge() ;
}
}
mapper = (Person person) -> person.getAge() ;
41. La façon du JDK 8
Prenons l’exemple du Mapper
mapper = new Mapper<Person, Integer>() {
public Integer map(Person person) { // 1 méthode
return person.getAge() ;
}
}
mapper = (Person person) -> person.getAge() ;
Le compilateur reconnaît cette expression comme une
implémentation du mapper
42. Que se passe-t-il si …
… il y a plus d’une ligne de code ?
mapper = (Person person) -> {
System.out.println("Mapping " + person) ;
return person.getAge() ;
}
Accolades, un return explicite
43. Que se passe-t-il si …
…le type de retour est void ?
consumer = (Person person) -> p.setAge(p.getAge() + 1) ;
44. Que se passe-t-il si …
…la méthode prend plus d’un argument ?
reducer = (int i1, int i2) -> {
return i1 + i2 ;
}
Ou :
reducer = (int i1, int i2) -> i1 + i2 ;
45. La façon du JDK 8
Comment le compilateur reconnaît-il l’implémentation du
mapper ?
mapper = (Person person) -> person.getAge() ;
46. La façon du JDK 8
Comment le compilateur reconnaît-il l’implémentation du
mapper ?
mapper = (Person person) -> person.getAge() ;
1) Il ne faut qu’une méthode dans le mapper
47. La façon du JDK 8
Comment le compilateur reconnaît-il l’implémentation du
mapper ?
mapper = (Person person) -> person.getAge() ;
1) Il ne faut qu’une méthode dans le mapper
2) Les types des paramètres et le type de retour doivent
être compatible
48. La façon du JDK 8
Comment le compilateur reconnaît-il l’implémentation du
mapper ?
mapper = (Person person) -> person.getAge() ;
1) Il ne faut qu’une méthode dans le mapper
2) Les types des paramètres et le type de retour doivent
être compatible
3) Les exceptions jetées doivent être compatibles
50. D’autres lambdas
Et la plupart du temps, le compilateur reconnaît ceci :
mapper = person -> person.getAge() ; // mapper
filter = age -> age > 20 ; // filter
reducer = (i1, i2) -> i1 + i2 ; // reducer
Le type des paramètres peut être omis
51. Une remarque sur la réduction
Comment cela fonctionne-t-il réellement ?
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62. Réduction
2 exemples :
Reducer r1 = (i1, i2) -> i1 + i2 ; // Ok
Reducer r2 = (i1, i2) -> i1*i1 + i2*i2 ; // Oooops
Attention :
le résultat est toujours reproductible en série
il ne l’est en général pas en parallèle
63. Pour le moment
Une expression lambda est une autre façon
d’écrire des instances de classes anonymes
64. Il y a d’autres syntaxes
On peut écrire :
mapper = person -> person.getAge() ;
Mais on peut aussi écrire :
mapper = Person::getAge ; // méthode non statique
65. Il y a d’autres syntaxes
On peut écrire :
sum = (i1, i2) -> i1 + i2 ;
sum = Integer::sum ; // méthode statique, nouvelle !
Ou encore :
max = (i1, i2) -> i1 > i2 ? i1 : i2 ;
max = Integer::max ; // méthode statique, nouvelle !
66. Il y a d’autres syntaxes
Encore un autre exemple :
toLower = String::toLowerCase ;
// !!!! NON NON NON !!!!
toLowerFR = String::toLowerCase(Locale.FRANCE) ;
70. Questions :
Comment modéliser une expression lambda ?
Puis-je mettre une expression lambda dans une variable ?
Un lambda est-il un objet ?
71. Modélisation
Un lambda = instance d’une « interface fonctionnelle »
@FunctionalInterface
public interface Consumer<T> {
public void accept(T t) ;
}
72. Modélisation
Un lambda = instance d’une « interface fonctionnelle »
@FunctionalInterface
public interface Consumer<T> {
public void accept(T t) ;
}
- ne possède qu’une unique méthode
73. Modélisation
Un lambda = instance d’une « interface fonctionnelle »
@FunctionalInterface
public interface Consumer<T> {
public void accept(T t) ;
}
- ne possède qu’une unique méthode
- peut être annotée par @FunctionalInterface (optionnel)
74. Mettre un lambda dans une variable
Exemple d’un consommateur :
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s) ;
}
} ;
Donc :
Consumer<String> c = s -> System.out.println(s) ;
75. Mettre un lambda dans une variable
Exemple d’un consommateur :
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s) ;
}
} ;
Question : qu’est-ce que s ?
Donc :
Consumer<String> c = s -> System.out.println(s) ;
76. Mettre un lambda dans une variable
Exemple d’un consommateur :
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s) ;
}
} ;
Donc :
Réponse : le
compilateur infère qu’il
s’agit d’un String
Consumer<String> c = s -> System.out.println(s) ;
77. Mettre un lambda dans une variable
Question : peut-on écrire ce code ?
int i = ... ;
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("i = " + i) ;
}
} ;
78. Mettre un lambda dans une variable
Question : peut-on écrire ce code ?
int i = ... ;
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("i = " + i) ;
}
} ;
JDK 7 : i doit être final
79. Mettre un lambda dans une variable
Question : peut-on écrire ce code ?
int i = ... ;
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("i = " + i) ;
}
} ;
JDK 8 : ce code compile
80. Mettre un lambda dans une variable
En revanche, ce code …
int i = ... ;
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("i = " + i) ;
}
} ;
i = i + 1 ;
… ne compile pas
81. Mettre un lambda dans une variable
Raison : le compilateur infère que i est effectivement final
final int i = ... ;
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("i = " + i) ;
}
} ;
i = i + 1 ; // on ne peut pas modifier i
82. Note au sujet de this
JDK 7 : this est l’instance anonyme
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("this = " + this) ;
}
public String toString() {
return "je suis dans c" ;
}
} ;
Appeler accept() affiche donc « je suis dans c »
83. Note au sujet de this
JDK 8 : idem, accept() affiche « je suis dans c »
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("this = " + this) ;
}
public String toString() {
return "je suis dans c" ;
}
} ;
Mais …
84. Note au sujet de this
Un consommateur peut aussi s’écrire comme ça :
Consumer<String> c = s -> System.out.println(this) ;
Dans ce cas, this est l’instance englobante
85. Questions :
Quel modèle pour un lambda ?
réponse : une interface fonctionnelle
Puis-je mettre un lambda dans une variable ?
réponse : oui
Un lambda est-il un objet ?
86. Un lambda est-il un objet ?
Petit jeu des 7 erreurs (avec une seule erreur)
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s) ;
}
} ;
Consumer<String> c = s -> System.out.println(s) ;
87. Un lambda est-il un objet ?
Petit jeu des 7 erreurs (avec une seule erreur)
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s) ;
}
} ;
Consumer<String> c = s -> System.out.println(s) ;
88. Questions :
Quel modèle pour un lambda ?
réponse : une interface fonctionnelle
Puis-je mettre un lambda dans une variable ?
réponse : oui
Un lambda est-il un objet ?
réponse : non
93. Consumer
Un consommateur consomme un objet
public interface Consumer<T> {
void accept(T t) ;
}
Consumer<String> c1 = s -> System.out.println(s) ;
Consumer<String> c2 = ... ;
Consumer<String> c3 = c1.andThen(c2) ;
persons.stream().forEach(c3) ;
94. BiConsumer
Prend deux arguments au lieu d’un
Peuvent être chaînés
Versions types primitifs :
- ObjIntConsumer
- ObjLongConsumer
- ObjDoubleConsumer
ObjIntConsumer prend un objet et un int en arguments
95. Function
Une fonction prend un objet et retourne un autre objet
public interface Function<T, R> {
R apply(T t) ;
}
Les fonctions peuvent être chaînées et / ou composées
BiFunction prend deux arguments au lieu d’un
UnaryOperator et BinaryOperator opèrent sur un seul type
96. Predicate
Un Predicate prend un objet et retourne un booléen
public interface Predicate<T> {
boolean test(T t) ;
}
Il peut être inversé, et composé avec des AND ou OR
97. BiPredicate
Un BiPredicate prend deux objets et retourne un booléen
public interface BiPredicate<T, U> {
boolean test(T t, U u) ;
}
Version pour les types booléens
99. La façon JDK 7
Comment implémenter le pattern map / filter / reduce
sur List<Person> ?
100. La façon JDK 7
Comment implémenter le pattern map / filter / reduce
sur List<Person> ?
La façon classique est d’itérer sur les éléments et
d’appliquer le pattern
101. La façon JDK 7
Comment implémenter le pattern map / filter / reduce
sur List<Person> ?
La façon classique est d’itérer sur les éléments et
d’appliquer le pattern
On peut pour cela créer une méthode helper
102. Mapping d’une liste
Mapping en JDK 8 avec des lambdas
List<Person> persons = new ArrayList<>() ;
List<Integer> ages =
Lists.map(
persons,
person -> person.getAge()
) ;
103. Mapping d’une liste
Mapping en JDK 8 avec des lambdas
List<Person> persons = new ArrayList<>() ;
List<Integer> ages =
Lists.map(
persons,
Person::getAge
) ;
104. Pattern complet
Le pattern va ressembler à ça :
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
105. Pattern complet
Le pattern va ressembler à ça :
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
L’idée est de pousser un lambda vers l’implémentation, et
de la laisser itérer en interne
106. Pattern complet
Le pattern va ressembler à ça :
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
Pour : l’API peut être optimisée sans
que l’on ait à toucher au code, super !
107. Pattern complet
Le pattern va ressembler à ça :
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
Pour : l’API peut être optimisée sans
que l’on ait à toucher au code, super !
Contre…
108. Pattern complet
1) Supposons que persons soit vraiment GRANDE
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
109. Pattern complet
1) Supposons que persons soit vraiment GRANDE
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
2 duplications : ages & agesGT20
110. Pattern complet
1) Supposons que persons soit vraiment GRANDE
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
2 duplications : ages & agesGT20
Que fait-on de ces listes ? On les envoie au GC !
111. Pattern complet
2) Supposons que les algorithmes de calcul du map / filter /
reduce soient déjà optimisés
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
112. Pattern complet
2) Supposons que les algorithmes de calcul du map / filter /
reduce soient déjà optimisés
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
… mais on doit encore gagner du temps !
113. Pattern complet
2) Supposons que les algorithmes de calcul du map / filter /
reduce soient déjà optimisés
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
Seule solution : paralléliser !
114. Pattern complet
2) Supposons que l’on veuille paralléliser…
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
Il faut mieux que ages et agesGT20 soient des collections
concurrentes !
115. Pattern complet
3) Supposons que nous aillons un reducer : allMatch
// pattern map / filter / reduce
List<Person> persons = ... ;
List<String> names =
Lists.map(persons, p -> p.getName()) ;
boolean allMatch =
Lists.allMatch(names, n -> n.length() < 20) ;
allMatch est vrai si tous les noms sont plus courts que 20
caractères
Voyons une implémentation possible de allMatch()
116. Écriture de allMatch()
Voici une implémentation basique de allMatch()
public static <T> boolean allMatch(
List<? extends T> list, Filter<T> filter) {
for (T t : list) {
if (!filter.filter(t)) {
return false ;
}
}
return true ;
}
117. Écriture de allMatch()
Voici une implémentation basique de allMatch()
public static <T> boolean allMatch(
List<? extends T> list, Filter<T> filter) {
for (T t : list) {
if (!filter.filter(t)) {
return false ;
}
}
return true ;
}
Pas besoin de parcourir
toute la liste !
118. Écriture de allMatch()
Voici une implémentation basique de allMatch()
public static <T> boolean allMatch(
List<? extends T> list, Filter<T> filter) {
for (T t : list) {
if (!filter.filter(t)) {
return false ;
}
}
return true ;
}
Sauf que…
119. Pattern complet
Quand on applique la réduction allMatch()…
// pattern map / filter / reduce
List<Person> persons = ... ;
List<String> names =
Lists.map(persons, p -> p.getName()) ;
boolean allMatch =
Lists.allMatch(names, n.length() < 20) ;
… names a déjà été évaluée !
120. Pattern complet
Quand on applique la réduction allMatch()…
// pattern map / filter / reduce
List<Person> persons = ... ;
List<String> names =
Lists.map(persons, p -> p.getName()) ;
boolean allMatch =
Lists.allMatch(names, n.length() < 20) ;
… names a déjà été évaluée !
On a perdu une belle optimisation…
121. Pattern complet
Quand on applique la réduction allMatch()…
// pattern map / filter / reduce
List<Person> persons = ... ;
List<String> names =
Lists.map(persons, p -> p.getName()) ;
boolean allMatch =
Lists.allMatch(names, n.length() < 20) ;
… names a déjà été évaluée !
Il aurait fallu appliquer le mapping de façon lazy
122. Conclusion
Pour : 1
Contre : 3 (au moins)
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
123. Conclusion
Pour : 1
Contre : 3 (au moins)
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= Lists.map(
persons, p -> p.getAge()) ;
List<Integer> agesGT20 = Lists.filter(ages,
a -> a > 20) ;
int sum
= Lists.reduce(agesGT20, (i1, i2) -> i1 + i2) ;
124. Conclusion
Et il en va de même pour celui-ci :
// pattern map / filter / reduce
List<Person> persons = ... ;
List<Integer> ages
= persons.map(p -> p.getAge()) ;
List<Integer> agesGT20 = ages.filter(a -> a > 20) ;
int sum
= agesGT20.reduce(ages, (i1, i2) -> i1 + i2) ;
125. Conclusion (again)
On a besoin d’un nouveau concept pour traiter les listes de
grande taille de façon efficace
126. Conclusion (again)
On a besoin d’un nouveau concept pour traiter les listes de
grande taille de façon efficace
Le framework Collection n’apporte pas de solution
satisfaisante
127. Conclusion (again)
On a besoin d’un nouveau concept pour traiter les listes de
grande taille de façon efficace
Le framework Collection n’apporte pas de solution
satisfaisante
On a besoin de quelque chose d’autre !
129. Introduction
Implémenter le map / filter / reduce sur Collection aurait
mené à ceci :
// map / filter / reduce pattern sur Collection
int sum = persons
.map(p -> p.getAge())
.filter(a -> a > 20)
.reduce(0, (a1, a2) -> a1 + a2) ;
Et même si cette approche n’est pas viable, c’est une
façon agréable d’écrire les choses
130. Introduction
Conservons le même genre de pattern, en ajoutant un
appel intermédiaire
// map / filter / reduce pattern sur Collection
int sum = persons.stream()
.map(p -> p.getAge())
.filter(a -> a > 20)
.reduce(0, (a1, a2) -> a1 + a2) ;
131. Introduction
Collection.stream() retourne un Stream : une nouvelle
interface
// map / filter / reduce pattern sur Collection
int sum = persons.stream()
.map(p -> p.getAge())
.filter(a -> a > 20)
.reduce(0, (a1, a2) -> a1 + a2) ;
Nouvelle interface = on a les mains libres !
132. Nouvelle interface Collection
Donc on a besoin d’une nouvelle méthode sur Collection
public interface Collection<E> {
// nos bonnes vieilles méthodes
Stream<E> stream() ;
}
133. Nouvelle interface Collection
Problème : ArrayList ne compile plus…
Une solution doit être trouvée !
public interface Collection<E> {
// nos bonnes vieilles méthodes
Stream<E> stream() ;
}
134. Interfaces
Problème : ArrayList ne compile plus…
Une solution doit être trouvée !
Une solution qui ne nécessite pas de modifier ou de
recompiler toutes les implémentations existantes de
Collection !
136. Interfaces Java 8
Problème : ArrayList ne compile plus…
Une solution doit être trouvée !
Une solution qui ne nécessite pas de modifier ou de
recompiler toutes les implémentations existantes de
Collection !
Problème : ajouter des méthodes à une interface sans
toucher aux implémentations…
137. Interfaces Java 8
Problème : ajouter des méthodes à une interface sans
toucher aux implémentations…
Solution : changer la façon dont les interfaces fonctionnent
en Java !
138. Interfaces Java 8
ArrayList a besoin de l’implémentation de stream()…
public interface Collection<E> {
// nos bonnes vieilles méthodes
Stream<E> stream() ;
}
139. Interfaces Java 8
Solution : mettons-les dans l’interface !
public interface Collection<E> {
// nos bonnes vieilles méthodes
default Stream<E> stream() {
return ... ;
}
}
140. Interfaces Java 8
Introduisons les default methods en Java
public interface Collection<E> {
// nos bonnes vieilles méthodes
default Stream<E> stream() {
return ... ;
}
}
Les méthodes par défaut permettent de faire évoluer de
vieilles interfaces
143. Méthodes par défaut
Cela amène-t-il l’héritage mutliple en Java ?
Oui, mais on l’a déjà
public class String
implements Serializable, Comparable<String>, CharSequence {
// ...
}
145. Méthodes par défaut
Ce que l’on a en Java est l’héritage multiple de type
Java 8 amène l’héritage multiple d’implémentation
146. Méthodes par défaut
Ce que l’on a en Java est l’héritage multiple de type
Java 8 amène l’héritage multiple d’implémentation
Ce que l’on a pas, c’est l’héritage multiple d’état
147. Méthodes par défaut
Ce que l’on a en Java est l’héritage multiple de type
Java 8 amène l’héritage multiple d’implémentation
Ce que l’on a pas, c’est l’héritage multiple d’état
… et d’ailleurs, on n’en veut pas !
152. Méthodes par défaut
public class C
implements A, B {
// ...
}
public interface A {
default String a() { ... }
}
public interface B {
default String a() { ... }
}
153. Méthodes par défaut
public class C
implements A, B {
public String a() { ... }
}
Exemple #0
public interface A {
default String a() { ... }
}
public interface B {
default String a() { ... }
}
La classe gagne !
L’implémentation efface la méthode par défaut
154. Méthodes par défaut
public class C
implements A, B {
// ...
}
Exemple #1
public interface A {
default String a() { ... }
}
public interface B {
default String a() { ... }
}
155. Méthodes par défaut
public class C
implements A, B {
// ...
}
Exemple #1
public interface A {
default String a() { ... }
}
public interface B {
default String a() { ... }
}
Erreur de compilation :
class C inherits unrelated defaults for a() from types A and B
156. Méthodes par défaut
public class C
implements A, B {
// ...
}
Exemple #2
public interface A extends B {
default String a() { ... }
}
public interface B {
default String a() { ... }
}
A est plus spécifique que B : A.a() a la priorité
157. Méthodes par défaut
public class D extends C
implements B {
// ...
}
public class C
implements A {
// ...
}
Exemple #2
public interface A extends B {
default String a() { ... }
}
public interface B {
default String a() { ... }
}
158. Méthodes par défaut
public class D extends C
implements B {
// ...
}
public class C
implements A {
// ...
}
Exemple #2
public interface A extends B {
default String a() { ... }
}
public interface B {
default String a() { ... }
}
A est toujours plus spécifique que B : A.a() gagne
159. Méthodes par défaut
public class C
implements A, B {
public String a() { return B.super.a() ; }
}
Retour sur l’exemple #1
public interface A {
default String a() { ... }
}
public interface B {
default String a() { ... }
}
On peut aussi faire un appel explicite
161. 2 règles simples
Pour gérer les conflits de l’héritage multiple d’implémentation
1) La classe gagne
162. 2 règles simples
Pour gérer les conflits de l’héritage multiple d’implémentation
1) La classe gagne
2) Les implémentations les plus spécifiques gagnent
163. Un exemple
On a tous écrit une implémentation d’Iterator :
public class MyIterator implements Iterator<E> {
// du code métier super important
public void remove() {
throw new UnsupportedOperationException("Naaaaaaan !") ;
} ;
}
164. Un exemple
Grâce à Java 8 :
public interface Iterator<E> {
default void remove() {
throw new UnsupportedOperationException("remove") ;
} ;
}
Plus besoin d’écrire ce code !
165. Interfaces en Java 8
Donc on change le concept d’interfaces en Java 8
public class HelloWorld {
// souvenir souvenir
public static void main(String[] args) {
System.out.println("Hello world!") ;
} ;
}
166. Interfaces en Java 8
Donc on change le concept d’interfaces en Java 8
Vraiment…
public interface HelloWorld {
// souvenir souvenir
public static void main(String[] args) {
System.out.println("Hello world!") ;
} ;
}
167. Interfaces en Java 8
1) Méthodes par défaut, héritage multiple
d’implémentation
règles pour gérer les conflits, qui s’appliquent à la compilation
2) Des méthodes statiques dans les interfaces
Tout ceci va permettre de nouvelles manières
d’écrire les APIs
168. On en sommes-nous ?
1) On a une nouvelle syntaxe : les lambdas
2) On a un pattern à implémenter
3) On a des nouvelles interfaces qui permettent de
conserver la compatibilité ascendante
170. Qu’est-ce qu’un Stream ?
D’un point de vue technique : une interface paramétrée
« un stream de String »
171. Qu’est-ce qu’un Stream ?
D’un point de vue technique : une interface paramétrée
« un stream de String »
D’autres interfaces pour les types primitifs :
IntStream, LongStream, DoubleStream
172. Une nouvelle notion
Cela ressemble à une collection, mais…
- Un Stream est construit sur une « source »
- Pas de donnée à la construction
- Pas de limite sur ce qu’une source peut produire
173. Une nouvelle notion
Un Stream peut être construit sur :
- Une collection ou un tableau
- Un itérateur
- Un source I/O
Certaines de ces sources sont « infinies »
174. Une nouvelle notion
1) Un stream ne porte pas de donnée
Il s’agit juste d’un objet sur lequel on peut déclarer des
opérations
175. Une nouvelle notion
1) Un stream ne porte pas de donnée
2) Un stream ne peut pas modifier sa source
Conséquence : il peut mener ses traitements en parallèle
176. Une nouvelle notion
1) Un stream ne porte pas de donnée
2) Un stream ne peut pas modifier sa source
3) Une source peut être infinie, ou non bornée
On a donc besoin de garantir que les calculs seront menés
en temps fini
177. Une nouvelle notion
1)
2)
3)
4)
Un stream ne porte pas de donnée
Un stream ne peut pas modifier sa source
Une source peut être infinie, ou non bornée
Un Stream traite ses données de façon lazy
On peut optimiser les opérations entre elles
On a besoin d’un mécanisme pour déclencher les
traitements
179. Un Stream est-il une Collection ?
La réponse est non
Une collection permet d’itérer sur ses éléments
180. Un Stream est-il une Collection ?
La réponse est non
Une collection permet d’itérer sur ses éléments
Pas un Stream
181. Un Stream est-il une Collection ?
La réponse est non
Une collection ne permet pas d’appliquer un lambda sur
ses éléments
182. Un Stream est-il une Collection ?
La réponse est non
Une collection ne permet pas d’appliquer un lambda sur
ses éléments
Un Stream le peut
183. Comment construire un Stream ?
De nombreuses façons de faire…
1) À partir d’une collection :
Collection<String> collection = ... ;
Stream<String> stream = collection.stream() ;
184. Comment construire un Stream ?
De nombreuses façons de faire…
1) À partir d’une collection :
2) À partir d’un tableau :
Stream<String> stream2 =
Arrays.stream(new String [] {"one", "two", "three"}) ;
185. Comment construire un Stream ?
De nombreuses façons de faire…
1) À partir d’une collection :
2) À partir d’un tableau :
3) À partir des méthodes factory de Stream
Stream<String> stream1 = Stream.of("one", "two", "three") ;
186. Comment construire un Stream ?
Encore quelques patterns :
Stream.empty() ; // Stream vide
Stream.of(T t) ; // un seul élément
Stream.generate(Supplier<T> s) ;
Stream.iterate(T seed, UnaryOperator<T> f) ;
187. Comment construire un Stream ?
Encore d’autres façons :
string.chars() ; // retourne un IntStream
lineNumberReader.lines() ; // retourne un Stream<String>
random.ints() ; // retourne un IntStream
188. Un premier exemple
Retour sur notre map / filter / reduce
Construction d’un
collections
Stream sur une List
// map / filter / reduce pattern on
int sum = persons.stream()
.map(p -> p.getAge())
.filter(a -> a > 20)
.reduce(0, (a1, a2) -> a1 + a2) ;
189. Un premier exemple
Retour sur notre map / filter / reduce
Déclarations
// map / filter / reduce pattern on collections
int sum = persons.stream()
.map(p -> p.getAge())
.filter(a -> a > 20)
.reduce(0, (a1, a2) -> a1 + a2) ;
190. Un premier exemple
Retour sur notre map / filter / reduce
On lance le
collections
calcul
// map / filter / reduce pattern on
int sum = persons.stream()
.map(p -> p.getAge())
.filter(a -> a > 20)
.reduce(0, (a1, a2) -> a1 + a2) ;
191. Un premier exemple
Retour sur notre map / filter / reduce
// map / filter / reduce pattern on collections
int sum = persons.stream()
.map(p -> p.getAge())
.filter(a -> a > 20)
.reduce(0, (a1, a2) -> a1 + a2) ;
Valeur par défaut, dans le
cas d’un stream vide
192. Deux types d’opérations
On peut donc déclarer des opérations sur un Stream
Deux types d’opérations :
1) Les opérations intermédiaires
Exemple : map, filter
193. Deux types d’opérations
On peut donc déclarer des opérations sur un Stream
Deux types d’opérations :
1) Les opérations intermédiaires
Exemple : map, filter
2) Les opérations terminales, qui déclenchent le traitement
Exemple : reduce
194. Un Stream possède un état
Les implémentations de Stream possèdent un état :
- SIZED = le cardinal du Stream est connu
- ORDERED = l’ordre du Stream est important (List)
- DISTINCT = pas de doublon dans le Stream (Set)
- SORTED = les Stream est trié (SortedSet)
195. Un Stream possède un état
Certaines opérations changent cet état
- le filtrage annule SIZED
- le mapping annule DISTINCT et SORTED
196. Un Stream possède un état
Et cela permet d’optimiser !
- un HashSet ne peut pas avoir de doublons…
197. Un Stream possède un état
Et cela permet d’optimiser !
- un HashSet ne peut pas avoir de doublons…
- donc distinct() ne fait rien (NOP) pour un stream
construit sur un HashSet
198. Un Stream possède un état
Et cela permet d’optimiser !
- un HashSet ne peut pas avoir de doublons…
- donc distinct() ne fait rien (NOP) pour un stream
construit sur un HashSet
- un TreeSet est trié, donc…
199. Un Stream possède un état
Et cela permet d’optimiser !
- un HashSet ne peut pas avoir de doublons…
- donc distinct() ne fait rien (NOP) pour un stream
construit sur un HashSet
- un TreeSet est trié, donc…
- sort() ne fait rien pour un stream construit sur un
TreeSet
200. Un
ème
2
exemple
Trions une liste de chaîne de caractères
List<String> strings = new ArrayList<>() ;
// on remplit la liste
// et plus loin dans le code on a ça :
List<String> result = strings.stream()
.sorted()
.collect(Collector.toList()) ;
201. Un
ème
2
exemple
Trions une liste de chaîne de caractères
// un jour un petit malin change l’implémentation
SortedSet<String> strings = new TreeSet<>() ;
// on remplit la liste
// et plus loin dans le code on a ça :
List<String> result = strings.stream()
.sorted()
.collect(Collector.toList()) ;
202. Un
ème
2
exemple
Trions une liste de chaîne de caractères
// un jour un petit malin change l’implémentation
SortedSet<String> strings = new TreeSet<>() ;
// on remplit la liste
// et plus loin dans le code on a ça :
List<String> result = strings.stream()
.sorted()
.collect(Collector.toList()) ;
Dans ce cas le code de tri n’est plus exécuté !
203. Opérations stateless / statefull
Opérations Stateless / statefull
Certaines opérations sont stateless :
persons.stream().map(p -> p.getAge()) ;
Cela signifie que l’on n’a pas besoin de plus d’information
que ce qui est contenu dans p
204. Opérations stateless / statefull
Opérations Stateless / statefull
Certaines opérations sont stateless :
persons.stream().map(p -> p.getAge()) ;
Pas le cas de toutes les opérations
stream.limit(10_000_000) ; // select les premiers 10M éléments
205. Exemple
Trions un tableau de String
Random rand = new Random() ;
String [] strings = new String[10_000_000] ;
for (int i = 0 ; i < strings.length ; i++) {
strings[i] = Long.toHexString(rand.nextLong()) ;
}
206. Exemple
Trions un tableau de String
Random rand = new Random() ;
String [] strings = new String[10_000_000] ;
for (int i = 0 ; i < strings.length ; i++) {
strings[i] = Long.toHexString(rand.nextLong()) ;
}
Soooo Java 7…
207. Exemple
Trions un tableau de String
Random rand = new Random() ;
Stream<String> stream =
Stream.generate(
() ->
Long.toHexString(rand.nextLong())
) ;
Meilleur ?
208. Exemple
Trions un tableau de String
// Random rand = new Random() ;
Stream<String> stream =
Stream.generate(
() ->
Long.toHexString(ThreadLocalRandom.current().nextLong())
) ;
Meilleur !
209. Exemple
Trions un tableau de String
// other way
Stream<String> stream =
ThreadLocalRandom
.current()
.longs() // returns a LongStream
.mapToObj(l -> Long.toHexString(l)) ;
210. Exemple
Trions un tableau de String
// other way
Stream<String> stream =
ThreadLocalRandom
.current()
.longs()
.mapToObj(Long::toHexString) ;
211. Exemple
Trions un tableau de String
// other way
Stream<String> stream =
ThreadLocalRandom
.current()
.longs()
.mapToObj(Long::toHexString)
.limit(10_000_000)
.sorted() ;
T = 4 ms
212. Exemple
Trions un tableau de String
// other way
Stream<String> stream =
ThreadLocalRandom
.current()
.longs()
.mapToObj(Long::toHexString)
.limit(10_000_000)
.sorted() ;
Object [] sorted = stream.toArray() ;
213. Exemple
Trions un tableau de String
// other way
Stream<String> stream =
ThreadLocalRandom
.current()
.longs()
.mapToObj(Long::toHexString)
.limit(10_000_000)
.sorted() ;
Object [] sorted = stream.toArray() ;
T = 14 s
214. Example
Trions un tableau de String
// other way
Stream<String> stream =
ThreadLocalRandom
.current()
.longs()
.mapToObj(Long::toHexString)
.limit(10_000_000)
.sorted() ;
Opérations
intermédiares !
Object [] sorted = stream.toArray() ;
T = 14 s
215. Au final
1) Il y a des opérations intermédiaires, et des opérations
terminales
2) Seule une opération terminale déclenche les
traitements
216. Au final
1) Il y a des opérations intermédiaires, et des opérations
terminales
2) Seule une opération terminale déclenche les
traitements
3) Une seule opération terminale est autorisée
4) Un Stream ne peut traité qu’une seule fois
Si besoin, un autre Stream doit être construit
218. Optimisation
La première optimisation (après l’exécution lazy) est le
parallélisme
Fork / join permet la programmation parallèle depuis le
JDK 7
Écrire du code parallèle reste complexe, et ne mène pas
toujours à de meilleures performances
219. Optimisation
La première optimisation (après l’exécution lazy) est le
parallélisme
Fork / join permet la programmation parallèle depuis le
JDK 7
Écrire du code parallèle reste complexe, et ne mène pas
toujours à de meilleures performances
Utiliser l’API Stream est plus simple et plus sûr
220. Construction d’un Stream parallèle
Deux patterns
1) Appeler parallelStream() au lieu de stream()
Stream<String> s = strings.parallelStream() ;
2) Appeler parallel() sur un stream existant
Stream<String> s = strings.stream().parallel() ;
225. Paralléliser est-il aussi simple ?
Exemple 1 :
persons.stream().limit(10_000_000) ;
« retourne les premiers 10M éléments »
226. Paralléliser est-il aussi simple ?
Exemple 1 :
persons.parallelStream().limit(10_000_000) ;
« retourne les premiers 10M éléments »
227. Paralléliser est-il aussi simple ?
Exemple 1 :
persons.parallelStream().limit(10_000_000) ;
« retourne les premiers 10M éléments »
Comment peut-on tracer que les éléments sont les
premiers sur un CPU multicœur ?
228. Paralléliser est-il aussi simple ?
Exemple 1 : performances
Code 1
List<Long> list = new ArrayList<>(10_000_100) ;
for (int i = 0 ; i < 10_000_000 ; i++) {
list1.add(ThreadLocalRandom.current().nextLong()) ;
}
230. Paralléliser est-il aussi simple ?
Exemple 1 : performances
Code 3
Stream<Long> stream =
ThreadLocalRandom.current().longs(10_000_000).mapToObj(Long::new) ;
List<Long> list = stream.collect(Collectors.toList()) ;
231. Paralléliser est-il aussi simple ?
Exemple 1 : performances
Code 1 (for)
Code 2 (limit)
Code 3 (longs)
Série
270 ms
310 ms
250 ms
Parallèle
232. Paralléliser est-il aussi simple ?
Exemple 1 : performances
Code 1 (for)
Code 2 (limit)
Code 3 (longs)
Série
270 ms
310 ms
250 ms
Parallèle
500 ms
320 ms
233. Paralléliser est-il aussi simple ?
Exemple 2 :
Stream s3 = Stream.concat(stream1, stream2) ;
« retourne un Stream composé des éléments du premier
Stream, puis des éléments du second »
234. Parallélisme
Le parallélisme implique des calculs supplémentaires la
plupart du temps
Des opérations mal configurées vont entraîner des calculs
inutiles, qui vont affaiblir les performances globales
Exemple : un stream ORDERED est plus complexe à
traiter
236. Qu’est-ce qu’une réduction ?
2 types de réduction :
1) La réduction
« A reduction operation (also called a fold) takes a
sequence of input elements and combines them
into a single summary result by repeated
application of a combining operation »
237. Qu’est-ce qu’une réduction ?
2 types de réduction :
2) La réduction mutable
« A mutable reduction operation accumulates
input elements into a mutable result container,
such as a Collection or StringBuilder, as it
processes the elements in the stream »
238. Une réduction simple
La somme des âges
// map / filter / reduce pattern on collections
int sum = persons.stream()
.map(p -> p.getAge())
.filter(a -> a > 20)
.reduce(0, (a1, a2) -> a1 + a2) ;
239. Une réduction simple
Ca serait bien de pouvoir écrire :
// map / filter / reduce pattern on collections
int sum = persons.stream()
.map(p -> p.getAge())
.filter(a -> a > 20)
.sum() ;
Mais sum() n’est pas définie sur Stream<T>
Que voudrait dire « additionner des personnes » ?
240. Une réduction simple
Ca serait bien de pouvoir écrire :
// map / filter / reduce pattern on collections
int sum = persons.stream()
.map(p -> p.getAge())
.filter(a -> a > 20)
.sum() ;
Mais sum() n’est pas définie sur Stream<T>
Mais il y a une méthode sum() sur IntStream !
241. Une réduction simple
2ème version :
// map / filter / reduce pattern on collections
int sum = persons.stream()
.map(Person::getAge)
.filter(a -> a > 20)
.mapToInt(Integer::intValue)
.sum() ;
Résultat pour une liste vide : 0
242. Une réduction simple
2ème version (encore meilleure) :
// map / filter / reduce pattern on collections
int sum = persons.stream()
.mapToInt(Person::getAge)
.filter(a -> a > 20)
// .mapToInt(Integer::intValue)
.sum() ;
Résultat pour une liste vide : 0
243. Une réduction simple
Qu’en est-il de min() et de max() ?
// map / filter / reduce pattern on collections
....... = persons.stream()
.mapToInt(Person::getAge)
.filter(a -> a > 20)
.max() ;
Quelle valeur par défaut pour max() ?
244. Problème des valeurs par défaut
La notion de « valeur par défaut » est plus complexe qu’il y
paraît…
245. Problème des valeurs par défaut
La notion de « valeur par défaut » est plus complexe qu’il y
paraît…
1) La « valeur par défaut » est la réduction de l’ensemble
vide
246. Problème des valeurs par défaut
La notion de « valeur par défaut » est plus complexe qu’il y
paraît…
1) La « valeur par défaut » est la réduction de l’ensemble
vide
2) Mais aussi l’élément neutre de la réduction
247.
248.
249.
250.
251.
252.
253.
254.
255. Problème des valeurs par défaut
La notion de « valeur par défaut » est plus complexe qu’il y
paraît…
1) La « valeur par défaut » est la réduction de l’ensemble
vide
2) Mais aussi l’élément neutre de la réduction
256. Problème des valeurs par défaut
Problème : max() and min() n’ont pas d’élément neutre
ie : un élément e pour lequel max(e, a) = a
257. Problème des valeurs par défaut
Problème : max() and min() n’ont pas d’élément neutre
ie : un élément e pour lequel max(e, a) = a
Ca ne peut pas être 0 : max(-1, 0) = 0
−∞ n’est pas un entier
258. Problème des valeurs par défaut
Donc quelle est la valeur par défaut de max() et min() ?
259. Problème des valeurs par défaut
Donc quelle est la valeur par défaut de max() et min() ?
Réponse : il n’y a pas de valeur par défaut pour max() et
pour min()
260. Problème des valeurs par défaut
Donc quel type choisir pour la méthode max() ?
// map / filter / reduce pattern on collections
....... = persons.stream()
.mapToInt(Person::getAge)
.filter(a -> a > 20)
.max() ;
261. Problème des valeurs par défaut
Donc quel type choisir pour la méthode max() ?
// map / filter / reduce pattern on collections
....... = persons.stream()
.mapToInt(Person::getAge)
.filter(a -> a > 20)
.max() ;
Si c’est int, la valeur par défaut sera 0…
262. Optionals
Sans valeur par défaut, il faut trouver autre chose…
// map / filter / reduce pattern on collections
OptionalInt optionalMax = persons.stream()
.mapToInt(Person::getAge)
.filter(a -> a > 20)
.max() ;
« il peut ne pas y avoir
de résultat »
263. Optionals
Que peut-on faire avec OptionalInt ?
1er pattern : tester s’il contient une valeur
OptionalInt optionalMax = ... ;
int max ;
if (optionalMax.isPresent()) {
max = optionalMax.get() ;
} else {
max = ... ; // décider d’une « valeur par défaut »
}
264. Optionals
Que peut-on faire avec OptionalInt ?
2ème pattern : lire la valeur ou jeter une exception
OptionalInt optionalMax = ... ;
// throws NoSuchElementException if no held value
int max = optionalMax.getAsInt() ;
265. Optionals
Que peut-on faire avec OptionalInt ?
3éme pattern : lire la valeur / retourner une valeur par défaut
OptionalInt optionalMax = ... ;
// get 0 if no held value
int max = optionalMax.orElse(0) ;
266. Optionals
Que peut-on faire avec OptionalInt ?
4ème pattern : lire la valeur ou jeter une exception
OptionalInt optionalMax = ... ;
// exceptionSupplier will supply an exception, if no held value
int max = optionalMax.orElseThrow(exceptionSupplier) ;
270. Mutable reductions : exemple 1
Utilisation d’une classe helper : Collectors
ArrayList<String> strings =
stream
.map(Object::toString)
.collect(Collectors.toList()) ;
271. Mutable reductions : exemple 2
Concaténation de String avec un helper
String names = persons
.stream()
.map(Person::getName)
.collect(Collectors.joining()) ;
272. Mutable reductions
Une réduction mutable dépend :
- d’un container : Collection or StringBuilder
- d’un moyen d’ajouter un élément au container
- d’un moyen de fusionner deux containers (utilisé dans
les opérations parallèles)
273. Mutable reductions
La forme générale a donc besoin de 3 objets :
- un supplier : construit une instance du container
Supplier<ArrayList<String>> supplier = () -> new ArrayList<String>() ;
274. Mutable reductions
La forme générale a donc besoin de 3 objets :
- un supplier : construit une instance du container
- un accumulateur : ajoute un élément au container
BiConsumer<ArrayList<String>, ? super String> accumulator =
(suppliedList, s) -> suppliedList.add(s) ;
275. Mutable reductions
La forme générale a donc besoin de 3 objets :
- un supplier : construit une instance du container
- un accumulateur : ajoute un élément au container
- un combiner : fusionne deux containers
BiConsumer<ArrayList<String>, ArrayList<String>> combiner =
(supplied1, supplied2) -> supplied1.addAll(supplied2) ;
276. Mutable reductions : exemple 1
D’où le code :
ArrayList<String> strings =
stream
.map(Object::toString)
.collect(
() -> new ArrayList<String>(), // the supplier
(suppliedList, s) -> suppliedList.add(s), // the accumulator
(supplied1, supplied2) -> supplied1.addAll(supplied2) // the combiner
) ;
277. Mutable reductions : exemple 1
D’où le code :
ArrayList<String> strings =
stream
.map(Object::toString)
.collect(
ArrayList::new,
// the supplier
ArrayList::add,
// the accumulator
ArrayList::addAll // the combiner
) ;
279. La classe Collectors
Une boite à outils (37 méthodes) pour la plupart des types
de réduction
- counting, minBy, maxBy
- summing, averaging, summarizing
- joining
- toList, toSet
Et
- mapping, groupingBy, partionningBy
280. La classe Collectors
Average, Sum, Count
persons
.stream()
.collect(Collectors.averagingDouble(Person::getAge)) ;
persons
.stream()
.collect(Collectors.counting()) ;
281. La classe Collectors
Concaténation des nom dans une String
String names = persons
.stream()
.map(Person::getName)
.collect(Collectors.joining(", ")) ;
283. La classe Collectors
Accumulation dans une collection passée en paramètre
TreeSet<Person> treeSetOfPersons = persons
.stream()
.collect(
Collectors.toCollection(TreeSet::new)) ;
284. La classe Collectors
Calculer un max avec un comparateur
Optional<Person> optionalPerson = persons
.stream()
.collect(
Collectors.maxBy(
Comparator.comparing(Person::getAge)) ;
Bonus : une API Comparator
285. Construction de comparateurs
Nouvelle API pour construire des comparateurs
Comparator<Person> comp =
Comparator.comparing(Person::getLastName)
.thenComparing(Person::getFirstName)
.thenComparing(Person:getAge) ;
286. Classe Collectors : mapping
La méthode mapping() prend 2 paramètres
- une fonction, qui mappe les éléments du Stream
- un collecteur, appelé « downstream », appliqué aux
valeurs mappées
287. Classe Collectors : mapping
Accumuler les noms des personnes dans un Set
Set<String> set = persons
.stream()
.collect(
Collectors.mapping(
Person::getLastName,
Collectors.toSet())) ;
288. Classe Collectors : mapping
Mapper le stream, accumulation dans une collection
TreeSet<String> set = persons
.stream()
.collect(
Collectors.mapping(
Person::getLastName,
Collectors.toCollection(TreeSet::new))
) ;
289. Classe Collectors : groupingBy
« Grouping by » construit des tables de hachage
- méthode de construction des clés
- par défaut les éléments sont rangés dans une liste
- on peut spécifier un downstream (collector)
290. Classe Collectors : groupingBy
Des personnes par âge
Map<Integer, List<Person>> map =
persons
.stream()
.collect(
Collectors.groupingBy(Person::getAge)) ;
291. Classe Collectors : groupingBy
… rangées dans des Set
Map<Integer, Set<Person>> map =
persons
.stream()
.collect(
Collectors.groupingBy(
Person::getAge,
Collectors.toSet() // le downstream
) ;
292. Classe Collectors : groupingBy
… on ne garde que les noms
Map<Integer, Set<String>> map =
persons
.stream()
.collect(
Collectors.groupingBy(
Person::getAge,
Collectors.mapping(
//
Person::getLastName, // the downstream
Collectors.toSet()
//
)
) ;
293. Classe Collectors : groupingBy
… les noms rangés dans des TreeSet
Map<Integer, TreeSet<String>> map =
persons
.stream()
.collect(
Collectors.groupingBy(
Person::getAge,
Collectors.mapping(
Person::getLastName,
Collectors.toCollection(TreeSet::new)
)
) ;
295. Classe Collectors : groupingBy
Exemple : création d’un histogramme des âges
Map<Integer, Long> map =
persons
.stream()
.collect(
Collectors.groupingBy(Person::getAge, Collectors.counting())
) ;
Donne le nombre de personnes par âge
296. Classe Collectors : partionningBy
Crée une Map<Boolean, …> à partir d’un prédicat
- la table a deux clés : TRUE et FALSE
- et on peut y ajouter un downstream
297. Classe Collectors : partionningBy
Crée une Map<Boolean, …> avec un prédicat
Map<Boolean, List<Person>> map =
persons
.stream()
.collect(
Collectors.partitioningBy(p -> p.getAge() > 20)
) ;
map.get(TRUE) retourne la liste des personnes de plus de
20 ans
298. Classe Collectors : partionningBy
On peut définir d’autres traitements
Map<Boolean, TreeSet<String>> map =
persons
.stream()
.collect(
Collectors.partitioningBy(
p -> p.getAge() > 20,
Collectors.mapping(
Person::getLastName,
Collectors.toCollection(TreeSet::new))
)
)
) ;
299. Classe Collectors : collectingAndThen
Collecte les données avec un downstream
Applique enfin une fonction appelée « finisher »
Indispensable pour retourner des collections immutables
300. Classe Collectors : collectingAndThen
Set<Map.Entry<Integer, List<Person>>> set =
persons
.stream()
.collect(
Collectors.collectingAndThen(
Collectors.groupingBy(
Person::getAge),
// downstream, construit une map
Map::entrySet
// finisher, appliqué à la map
) ;
Dans ce cas « Map::entrySet » est un finisher
302. er
1
exemple
Optional<Entry<Integer, Long>> opt =
movies.stream().parallel()
.collect(
Collectors.collectingAndThen(
Collectors.groupingBy(
movie -> movie.releaseYear(),
Collectors.counting()
),
Map::entrySet
)
)
.stream()
.max(Map.Entry.comparingByValue()) ;
303. er
1
exemple
Un stream de movies
Optional<Entry<Integer, Long>> opt =
movies.stream().parallel()
.collect(
Collectors.collectingAndThen(
Collectors.groupingBy(
movie -> movie.releaseYear(),
Collectors.counting()
),
Map::entrySet
)
)
.stream()
.max(Map.Entry.comparingByValue()) ;
304. er
1
exemple
Construction d’une map
année / # de films
Optional<Entry<Integer, Long>> opt =
movies.stream().parallel()
.collect(
Collectors.collectingAndThen(
Collectors.groupingBy(
movie -> movie.releaseYear(),
Collectors.counting()
),
Map::entrySet
)
)
.stream()
.max(Map.Entry.comparingByValue()) ;
305. er
1
exemple
Optional<Entry<Integer, Long>> opt =
movies.stream().parallel()
.collect(
Collectors.collectingAndThen(
Collectors.groupingBy(
movie -> movie.releaseYear(),
Collectors.counting()
),
Map::entrySet
)
)
.stream()
.max(Map.Entry.comparingByValue()) ;
Construction de
l’EntrySet
306. er
1
exemple
Et déterminer la plus
grande valeur
Optional<Entry<Integer, Long>> opt =
movies.stream().parallel()
.collect(
Collectors.collectingAndThen(
Collectors.groupingBy(
movie -> movie.releaseYear(),
Collectors.counting()
),
Map::entrySet
)
)
.stream()
.max(Map.Entry.comparingByValue()) ;
307. er
1
exemple
Retourne l’année qui a
vu le plus grand
nombre de films
Optional<Entry<Integer, Long>> opt =
movies.stream().parallel()
.collect(
Collectors.collectingAndThen(
Collectors.groupingBy(
movie -> movie.releaseYear(),
Collectors.counting()
),
Map::entrySet
)
)
.stream()
.max(Map.Entry.comparingByValue()) ;
309. Traitement parallèle
Deux façons de le faire :
- créer un stream parallèle d’entrée
- Appeler parallel() sur un stream donné, on peut aussi
appeler sequential()
Quand une opération terminale est appelée, l’ensemble
des opérations est exécuté en parallèle ou pas, en fonction
du mode du stream
310. Les choses qui ne marchent pas
Certaines opérations ne donnent pas des résultats
reproductibles en parallèle, ex : findAny()
On ne doit pas modifier la source durant le traitement, si on
le fait les résultats ne sont pas prévisibles, même si la
source est une collection concurrente
311. Les choses qui marchent pas, mais…
Le traitement parallèle doit être le moins contraint possible
pour être efficace
- ORDERED est une contrainte coûteuse
- collect() doit utiliser des structures concurrentes
317. Conclusion
Pourquoi les lambdas ont-ils été introduits dans Java 8 ?
Réponse : parce que c’est à la mode !
Parce que le code écrit est plus compact !
318. Plus c’est court, plus c’est bon !
Un exemple de code compact
#include "stdio.h"
main() {
int b=0,c=0,q=60,_=q;for(float i=-20,o,O=0,l=0,j,p;j=O*O,p=l*l,
(!_--|(j+p>4)?fputc(b?q+(_/3):10,(i+=!b,p=j=O=l=0,c++,stdout)),
_=q:l=2*O*l+i/20,O=j-p+o),b=c%q,c<2400;o=-2+b*.05) ;
}
http://www.codeproject.com/Articles/2228/Obfuscating-your-Mandelbrot-code
320. Conclusion
Pourquoi les lambdas ont-ils été introduits dans Java 8 ?
Réponse : parce que c’est à la mode !
Parce que le code écrit est plus compact !
321. Conclusion
Pourquoi les lambdas ont-ils été introduits dans Java 8 ?
Parce que les lambdas autorisent des nouveaux patterns
qui permettent de paralléliser les traitements simplement et
de façon sûre
- dont les applications ont besoin
- dont concepteurs d’API ont besoin
322. Conclusion
Java 8 arrive, il s’agit de la mise à jour la plus importante
depuis 15 ans que Java existe
323. Conclusion
Java 8 arrive, il s’agit de la mise à jour la plus importante
depuis 15 ans que Java existe
Migrer vers Java 8 va nécessiter du travail pour nous les
développeurs
- auto-formation
- changement de nos habitudes de travail
324. Conclusion
Java 8 arrive, il s’agit de la mise à jour la plus importante
depuis 15 ans que Java existe
Migrer vers Java 8 va nécessiter du travail pour nous les
développeurs
- auto-formation
- changement de nos habitudes de travail
- convaincre nos boss…
325. Conclusion
Java 8 arrive, il s’agit de la mise à jour la plus importante
depuis 15 ans que Java existe
Téléchargeons la version développeur sur openjdk.net
Date de sortie : 18 mars 2014
326. Conclusion
Java 8 arrive, il s’agit de la mise à jour la plus importante
depuis 15 ans que Java existe
« Java is a blue collar language. It’s not PhD thesis
material but a language for a job » – James Gosling, 1997
327. Conclusion
Java 8 arrive, il s’agit de la mise à jour la plus importante
depuis 15 ans que Java existe
« Language features are not a goal unto themselves;
language features are enablers, encouraging or
discouraging certain styles and idioms » – Brian Goetz,
2013
328. Conclusion
Java 8 arrive, il s’agit de la mise à jour la plus importante
depuis 15 ans que Java existe
La bonne nouvelle :
Java est toujours Java !