20140123 java8 lambdas_jose-paumard-soat

Post on 31-May-2015

155 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

JDK 8 & Lambdas

Lambdas, Streams, Collectors

Pourquoi ont-ils été introduits ?

Pourquoi ont-ils été introduits ?

Comment vont-ils changer nos habitudes de

programmation ?

Année d’édition

1994

Année d’édition

1994

En stock. Expédié et vendu par Amazon. Emballage cadeau disponible.

Formation Java 8

6 & 7 février 2014

BBL

Introduisons les lambdas

Introduisons les lambdas sur un exemple simple

Un exemple très simple

public class Person { private String name ; private int age ; // constructors // getters / setters }

List<Person> list = new ArrayList<>() ;

Un bon vieux bean

… et une bonne vieille liste

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() ; }

Calculons la moyenne des âges des personnes

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 ; }

Plus dur : pour les personnes de plus de 20 ans

« programmation impérative »

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 ; }

Plus dur : pour les personnes de plus de 20 ans

select avg(age) from Person where age > 20

… pas une obligation !

Ici on décrit le résultat

select avg(age) from Person where age > 20

Dans ce cas la base de données mène le calcul

comme elle l’entend

© SQL 1974

… pas une obligation !

Ici on décrit le résultat

Person age age > 20 sum

1ère étape : mapping

map

Mapping :

- prend une liste d’un type donné

- retourne une liste d’un autre type

- possède le même nombre d’éléments

1ère étape : mapping

map

Person age age > 20 sum

2ème étape : filtrage

filter map

Person age age > 20 sum

Filtrage :

- prend une liste d’un type donné

- retourne une liste du même type

- mais avec moins d’éléments

Person age age > 20 sum

2ème étape : filtrage

filter map

3ème étape : réduction

reduce filter map

Person age age > 20 sum

Reduction : agrégation des éléments d’une

liste dans un seul élément

Ex : moyenne, somme, min, max, etc…

Person age age > 20 sum

3ème étape : réduction

reduce filter map

Comment peut-on

modéliser ce traitement ?

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

Mapper<Person, Integer> mapper = new Mapper<Person, Integer>() { public Integer map(Person p) { return p.getAge() ; } }

La façon JDK 7

… et on crée une classe anonyme

public interface Mapper<T, V> { public V map(T t) ; }

AgePredicate predicate = new Predicate<Integer>() { public boolean filter(Integer i) { return i > 20 ; } }

public interface Predicate<T> { public boolean filter(T t) ; }

La façon JDK 7

On peut faire la même chose pour le filtrage

Reducer<Integer> reduction = new Reducer<Integer>() { public Integer reduce(Integer i1, Integer i2) { return i1 + i2 ; } }

public interface Reducer<T> { public T reduce(T t1, T t2) ; }

La façon JDK 7

Et enfin pour la réduction

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

La façon JDK 7

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

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

La façon JDK 7

Au final, le pattern map / filter / reduce en JDK 7 :

1) Créer 3 interfaces

2) Et on applique…

À la façon du JDK 8

mapper = new Mapper<Person, Integer>() { public Integer map(Person person) { return person.getAge() ; } }

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() ; } }

Prenons l’exemple du Mapper

La façon du JDK 8

mapper = new Mapper<Person, Integer>() { public Integer map(Person person) { // 1 méthode return p.getAge() ; } }

mapper = (Person person) ;

On prend

person

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) -> ;

et…

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() ;

… on retourne son âge

La façon du JDK 8

Prenons l’exemple du Mapper

Prenons l’exemple du Mapper

Le compilateur reconnaît cette expression comme une

implémentation du mapper

mapper = new Mapper<Person, Integer>() { public Integer map(Person person) { // 1 méthode return person.getAge() ; } }

mapper = (Person person) -> person.getAge() ;

La façon du JDK 8

Que se passe-t-il si …

… il y a plus d’une ligne de code ?

Accolades, un return explicite

mapper = (Person person) -> { System.out.println("Mapping " + person) ; return person.getAge() ; }

Que se passe-t-il si …

…le type de retour est void ?

consumer = (Person person) -> p.setAge(p.getAge() + 1) ;

Que se passe-t-il si …

…la méthode prend plus d’un argument ?

Ou :

reducer = (int i1, int i2) -> { return i1 + i2 ; }

reducer = (int i1, int i2) -> i1 + i2 ;

La façon du JDK 8

Comment le compilateur reconnaît-il l’implémentation du

mapper ?

mapper = (Person person) -> person.getAge() ;

Comment le compilateur reconnaît-il l’implémentation du

mapper ?

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

mapper = (Person person) -> person.getAge() ;

La façon du JDK 8

D’autres lambdas

On peut écrire d’autres lambdas facilement :

mapper = (Person person) -> person.getAge() ; // mapper filter = (int age) -> age > 20 ; // filter reducer = (int i1, int i2) -> i1 + i2 ; // reducer

Et la plupart du temps, le compilateur reconnaît ceci :

Le type des paramètres peut être omis

mapper = person -> person.getAge() ; // mapper filter = age -> age > 20 ; // filter reducer = (i1, i2) -> i1 + i2 ; // reducer

D’autres lambdas

Une remarque sur la réduction

Comment cela fonctionne-t-il réellement ?

Réduction

2 exemples :

Attention :

le résultat est toujours reproductible en série

il ne l’est en général pas en parallèle

Reducer r1 = (i1, i2) -> i1 + i2 ; // Ok Reducer r2 = (i1, i2) -> i1*i1 + i2*i2 ; // Oooops

Pour le moment

Une expression lambda est une autre façon

d’écrire des instances de classes anonymes

Il y a d’autres syntaxes

On peut écrire :

Mais on peut aussi écrire :

mapper = person -> person.getAge() ;

mapper = Person::getAge ; // méthode non statique

Il y a d’autres syntaxes

On peut écrire :

Ou encore :

sum = (i1, i2) -> i1 + i2 ; sum = Integer::sum ; // méthode statique, nouvelle !

max = (i1, i2) -> i1 > i2 ? i1 : i2 ; max = Integer::max ; // méthode statique, nouvelle !

Il y a d’autres syntaxes

Encore un autre exemple :

toLower = String::toLowerCase ;

// !!!! NON NON NON !!!! toLowerFR = String::toLowerCase(Locale.FRANCE) ;

Plus loin sur les lambdas

Questions :

Comment modéliser une expression lambda ?

Questions :

Comment modéliser une expression lambda ?

Puis-je mettre une expression lambda dans une variable ?

Questions :

Comment modéliser une expression lambda ?

Puis-je mettre une expression lambda dans une variable ?

Un lambda est-il un objet ?

Modélisation

Un lambda = instance d’une « interface fonctionnelle »

@FunctionalInterface public interface Consumer<T> { public void accept(T t) ; }

Modélisation

Un lambda = instance d’une « interface fonctionnelle »

- ne possède qu’une unique méthode

@FunctionalInterface public interface Consumer<T> { public void accept(T t) ; }

Modélisation

Un lambda = instance d’une « interface fonctionnelle »

- ne possède qu’une unique méthode

- peut être annotée par @FunctionalInterface (optionnel)

@FunctionalInterface public interface Consumer<T> { public void accept(T t) ; }

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

Consumer<String> c = s -> System.out.println(s) ;

Donc :

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 ?

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) ;

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) ;

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

Des lambdas à profusion :

Java.util.functions

Java.util.functions

C’est dans ce package que sont les interfaces

fonctionnelles

Il y en a 43

Java.util.functions

Supplier

Consumer / BiConsumer

Function / BiFunction (UnaryOperator / BinaryOperator)

Predicate / BiPredicate

En plus des versions construites sur les types primitifs

Supplier

Un supplier fournit un objet

public interface Supplier<T> { T get() ; }

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) ;

Function

Une fonction prend un objet et retourne un autre objet

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

public interface Function<T, R> { R apply(T t) ; }

Predicate

Un Predicate prend un objet et retourne un booléen

Il peut être inversé, et composé avec des AND ou OR

public interface Predicate<T> { boolean test(T t) ; }

Retour sur

map / filter / reduce

La façon JDK 7

Comment implémenter le pattern map / filter / reduce

sur List<Person> ?

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() ) ;

Mapping d’une liste

Mapping en JDK 8 avec des lambdas

List<Person> persons = new ArrayList<>() ; List<Integer> ages = Lists.map( persons, Person::getAge ) ;

Pattern complet

Le pattern va ressembler à ça :

Pour : l’API peut être optimisée sans

que l’on ait à toucher au code, super !

// 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) ;

Pattern complet

Le pattern va ressembler à ça :

Pour : l’API peut être optimisée sans

que l’on ait à toucher au code, super !

Contre…

// 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) ;

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) ;

Pattern complet

1) Supposons que persons soit vraiment GRANDE

2 duplications : ages & agesGT20

// 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) ;

Pattern complet

1) Supposons que persons soit vraiment GRANDE

2 duplications : ages & agesGT20

Que fait-on de ces listes ? On les envoie au GC !

// 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) ;

Pattern complet

2) Supposons que nous aillons un reducer : allMatch

allMatch est vrai si tous les noms sont plus courts que 20

caractères

Voyons une implémentation possible de 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) ;

É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 !

Pattern complet

Quand on applique la réduction allMatch()…

… names a déjà été évaluée !

// pattern map / filter / reduce List<Person> persons = ... ; List<String> names = Lists.map(persons, p -> p.getName()) ; boolean allMatch = Lists.allMatch(names, n.length() < 20) ;

Pattern complet

Quand on applique la réduction allMatch()…

… names a déjà été évaluée !

Il aurait fallu appliquer le mapping de façon lazy

// pattern map / filter / reduce List<Person> persons = ... ; List<String> names = Lists.map(persons, p -> p.getName()) ; boolean allMatch = Lists.allMatch(names, n.length() < 20) ;

Conclusion

Pour : 1

Contre : 2 (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) ;

// 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) ;

Conclusion

Pour : 1

Contre : 2 (au moins)

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) ;

Conclusion (again)

On a besoin d’un nouveau concept pour traiter les listes de

grande taille de façon efficace

Quel pattern choisir ?

Introduction

Implémenter le map / filter / reduce sur Collection aurait

mené à ceci :

Et même si cette approche n’est pas viable, c’est une

façon agréable d’écrire les choses

// map / filter / reduce pattern sur Collection int sum = persons .map(p -> p.getAge()) .filter(a -> a > 20) .reduce(0, (a1, a2) -> a1 + a2) ;

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) ;

Introduction

Collection.stream() retourne un Stream : une nouvelle

interface

Nouvelle interface = on a les mains libres !

// 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 Collection

Donc on a besoin d’une nouvelle méthode sur Collection

public interface Collection<E> { // nos bonnes vieilles méthodes Stream<E> stream() ; }

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() ; }

Nouvelles interfaces

Interfaces Java 8

Problème : ajouter des méthodes à une interface sans

toucher aux implémentations…

Problème : ajouter des méthodes à une interface sans

toucher aux implémentations…

Solution : changer la façon dont les interfaces fonctionnent

en Java !

Interfaces Java 8

Interfaces Java 8

ArrayList a besoin de l’implémentation de stream()…

public interface Collection<E> { // nos bonnes vieilles méthodes Stream<E> stream() ; }

Interfaces Java 8

Solution : mettons-les dans l’interface !

public interface Collection<E> { // nos bonnes vieilles méthodes default Stream<E> stream() { return ... ; } }

Interfaces Java 8

Une interface devient-elle une classe abstraite ?

Interfaces Java 8

Une interface devient-elle une classe abstraite ?

Non !

- une classe abstraite peut avoir des champs et possède

un constructeur

- un appel de méthode d’une classe abstraite (ou pas) est

résolu à l’exécution

- une implémentation par défaut est juste du code, lié à la

compilation

Méthodes par défaut

Cela amène-t-il l’héritage multiple en Java ?

Méthodes par défaut

Cela amène-t-il l’héritage multiple en Java ?

Oui, mais on l’a déjà

Méthodes par défaut

Cela amène-t-il l’héritage multiple en Java ?

Oui, mais on l’a déjà

public class String implements Serializable, Comparable<String>, CharSequence { // ... }

Méthodes par défaut

Ce que l’on a en Java est l’héritage multiple de type

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

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

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 !

Méthodes par défaut

Peut-on avoir des conflits ?

Méthodes par défaut

Peut-on avoir des conflits ?

Hélas oui…

Méthodes par défaut

Peut-on avoir des conflits ?

Hélas oui…

Il nous faut donc des règles pour les gérer

Méthodes par défaut

public class C implements A, B { // ... }

Méthodes par défaut

Exemple #1

public class C implements A, B { // ... }

public interface A { default String a() { ... } }

public interface B { default String a() { ... } }

Méthodes par défaut

Exemple #1

Erreur de compilation :

class C inherits unrelated defaults for a() from types A and B

public class C implements A, B { // ... }

public interface A { default String a() { ... } }

public interface B { default String a() { ... } }

Méthodes par défaut

Exemple #2

La classe gagne !

public class C implements A, B { public String a() { ... } }

public interface A { default String a() { ... } }

public interface B { default String a() { ... } }

Méthodes par défaut

Exemple #3

Le plus spécifique gagne !

public interface A extends B { default String a() { ... } }

public interface B { default String a() { ... } }

public class C implements A, B { // ... }

Méthodes par défaut

Retour sur l’exemple #1

On peut aussi faire un appel explicite

public interface A { default String a() { ... } }

public interface B { default String a() { ... } }

public class C implements A, B { public String a() { return B.super.a() ; } }

2 règles simples

Pour gérer les conflits de l’héritage multiple d’implémentation

2 règles simples

Pour gérer les conflits de l’héritage multiple d’implémentation

1) La classe gagne

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

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 !") ; } ; }

Un exemple

Grâce à Java 8 :

Plus besoin d’écrire ce code !

public interface Iterator<E> { default void remove() { throw new UnsupportedOperationException("remove") ; } ; }

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!") ; } ; }

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!") ; } ; }

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

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

L’API Stream

Qu’est-ce qu’un Stream ?

D’un point de vue technique : une interface paramétrée

« un stream de String »

Qu’est-ce qu’un Stream ?

D’un point de vue technique : une interface paramétrée

D’autres interfaces pour les types primitifs :

« un stream de String »

IntStream, LongStream, DoubleStream

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

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

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

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

4) 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

Comment construire un Stream ?

De nombreuses façons de faire…

1) À partir d’une collection :

Collection<String> collection = ... ; Stream<String> stream = collection.stream() ;

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"}) ;

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") ;

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) ;

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

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

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

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) ;

Construction d’un

Stream sur une List

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) ;

Déclarations

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) ;

On lance le

calcul

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

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)

Un Stream possède un état

Certaines opérations changent cet état

- le filtrage annule SIZED

- le mapping annule DISTINCT et SORTED

Un Stream possède un état

Et cela permet d’optimiser !

- un HashSet ne peut pas avoir de doublons…

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 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…

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…

- sorted() ne fait rien pour un stream construit sur un

TreeSet

Opérations stateless / statefull

Opérations Stateless / statefull

Certaines opérations sont stateless :

Cela signifie que l’on n’a pas besoin de plus d’information

que ce qui est contenu dans p

persons.stream().map(p -> p.getAge()) ;

Opérations stateless / statefull

Opérations Stateless / statefull

Certaines opérations sont stateless :

Pas le cas de toutes les opérations

persons.stream().map(p -> p.getAge()) ;

stream.limit(10_000_000) ; // sélection des premiers 10M éléments

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()) ; }

Exemple

Trions un tableau de String

Soooo Java 7…

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()) ; }

Exemple

Trions un tableau de String

Meilleur ?

Random rand = new Random() ; Stream<String> stream = Stream.generate( () -> Long.toHexString(rand.nextLong()) ) ;

Exemple

Trions un tableau de String

Meilleur !

// Random rand = new Random() ; Stream<String> stream = Stream.generate( () -> Long.toHexString(ThreadLocalRandom.current().nextLong()) ) ;

Exemple

Trions un tableau de String

// other way Stream<String> stream = ThreadLocalRandom .current() .longs() // returns a LongStream .mapToObj(l -> Long.toHexString(l)) ;

Exemple

Trions un tableau de String

// other way Stream<String> stream = ThreadLocalRandom .current() .longs() .mapToObj(Long::toHexString) ;

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

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() ;

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

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

Opérations

intermédiaires !

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

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

Parallel Streams

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

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

Construction d’un Stream parallèle

Deux patterns

1) Appeler parallelStream() au lieu de stream()

2) Appeler parallel() sur un stream existant

Stream<String> s = strings.parallelStream() ;

Stream<String> s = strings.stream().parallel() ;

Paralléliser est-il aussi simple ?

En fait oui !

Paralléliser est-il aussi simple ?

En fait oui !

… et non…

Paralléliser est-il aussi simple ?

Exemple 1 :

« retourne les premiers 10M éléments »

persons.stream().limit(10_000_000) ;

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()) ; }

Paralléliser est-il aussi simple ?

Exemple 1 : performances

Code 2

Stream<Long> stream = Stream.generate(() -> ThreadLocalRandom.current().nextLong()) ; List<Long> list1 = stream.limit(10_000_000).collect(Collectors.toList()) ;

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()) ;

Paralléliser est-il aussi simple ?

Exemple 1 : performances

Série Parallèle

Code 1 (for) 270 ms

Code 2 (limit) 310 ms

Code 3 (longs) 250 ms

Paralléliser est-il aussi simple ?

Exemple 1 : performances

Série Parallèle

Code 1 (for) 270 ms

Code 2 (limit) 310 ms 500 ms

Code 3 (longs) 250 ms 320 ms

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

Réductions

La réduction simple

La somme des âges

// map / filter / reduce int sum = persons.stream() .map(p -> p.getAge()) .filter(a -> a > 20) .reduce(0, (a1, a2) -> a1 + a2) ;

La réduction simple

Ca serait bien de pouvoir écrire :

Mais sum() n’est pas définie sur Stream<T>

Que voudrait dire « additionner des personnes » ?

// map / filter / reduce int sum = persons.stream() .map(p -> p.getAge()) .filter(a -> a > 20) .sum() ;

La réduction simple

Ca serait bien de pouvoir écrire :

Mais sum() n’est pas définie sur Stream<T>

Mais il y a une méthode sum() sur IntStream !

// map / filter / reduce int sum = persons.stream() .map(p -> p.getAge()) .filter(a -> a > 20) .sum() ;

La réduction simple

2ème version :

Résultat pour une liste vide : 0

// map / filter / reduce int sum = persons.stream() .map(Person::getAge) .filter(a -> a > 20) .mapToInt(Integer::intValue) .sum() ;

La réduction simple

2ème version (encore meilleure) :

Résultat pour une liste vide : 0

// map / filter / reduce int sum = persons.stream() .mapToInt(Person::getAge) .filter(a -> a > 20) // .mapToInt(Integer::intValue) .sum() ;

La réduction simple

Qu’en est-il de min() et de max() ?

Quelle valeur par défaut pour max() ?

// map / filter / reduce ....... = persons.stream() .mapToInt(Person::getAge) .filter(a -> a > 20) .max() ;

Problème des valeurs par défaut

La notion de « valeur par défaut » est plus complexe qu’il y

paraît…

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

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

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

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

Problème des valeurs par défaut

Donc quelle est la valeur par défaut de max() et min() ?

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()

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() ;

Problème des valeurs par défaut

Donc quel type choisir pour la méthode max() ?

Si c’est int, la valeur par défaut sera 0…

// map / filter / reduce pattern on collections ....... = persons.stream() .mapToInt(Person::getAge) .filter(a -> a > 20) .max() ;

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 »

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 » }

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() ;

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) ;

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) ;

Available Optionals

Optional<T>

OptionalInt, OptionalLong, OptionalDouble

Réductions disponibles

Sur Stream<T> :

- reduce()

- count(), min(), max()

- anyMatch(), allMatch(), noneMatch()

- findFirst(), findAny()

- toArray()

- forEach(), forEachOrdered()

Réductions disponibles

Sur IntStream, LongStream, DoubleStream :

- average()

- sum()

- summaryStatistics()

Mutable reductions

Mutable reductions : exemple 1

Utilisation d’une classe helper : Collectors

ArrayList<String> strings = stream .map(Object::toString) .collect(Collectors.toList()) ;

Mutable reductions : exemple 2

Concaténation de String avec un helper

String names = persons .stream() .map(Person::getName) .collect(Collectors.joining()) ;

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)

Collectors

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, toMap

Et

- mapping, groupingBy, partionningBy

La classe Collectors

Average, Sum, Count

persons .stream() .collect(Collectors.averagingDouble(Person::getAge)) ;

persons .stream() .collect(Collectors.counting()) ;

La classe Collectors

Concaténation des noms dans une String

String names = persons .stream() .map(Person::getName) .collect(Collectors.joining(", ")) ;

La classe Collectors

Accumulation dans un Set

Set<Person> setOfPersons = persons .stream() .collect( Collectors.toSet()) ;

La classe Collectors

Accumulation dans une collection passée en paramètre

TreeSet<Person> treeSetOfPersons = persons .stream() .collect( Collectors.toCollection(TreeSet::new)) ;

La classe Collectors

Calculer un max avec un comparateur

Bonus : une API Comparator

Optional<Person> optionalPerson = persons .stream() .collect( Collectors.maxBy( Comparator.comparing(Person::getAge)) ;

Construction de comparateurs

Nouvelle API pour construire des comparateurs

Comparator<Person> comp = Comparator.comparing(Person::getLastName) .thenComparing(Person::getFirstName) .thenComparing(Person::getAge) ;

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

Classe Collectors : mapping

Accumuler les noms des personnes dans un Set

Set<String> set = persons .stream() .collect( Collectors.mapping( Person::getLastName, Collectors.toSet())) ;

Classe Collectors : mapping

Mapper le stream, accumulation dans une collection

TreeSet<String> set = persons .stream() .collect( Collectors.mapping( Person::getLastName, Collectors.toCollection(TreeSet::new)) ) ;

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)

Classe Collectors : groupingBy

Des personnes par âge

Map<Integer, List<Person>> map = persons .stream() .collect( Collectors.groupingBy( Person::getAge)) ;

Classe Collectors : groupingBy

… rangées dans des Set

Map<Integer, Set<Person>> map = persons .stream() .collect( Collectors.groupingBy( Person::getAge, Collectors.toSet() // le downstream ) ;

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() // ) ) ;

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) ) ) ;

Classe Collectors : groupingBy

… etc… etc… etc…

TreeMap<Integer, TreeSet<String>> map = persons .stream() .collect( Collectors.groupingBy( Person::getAge, TreeMap::new, Collectors.mapping( Person::getLastName, Collectors.toCollection(TreeSet::new) ) ) ;

Classe Collectors : groupingBy

Exemple : création d’un histogramme des âges

Donne le nombre de personnes par âge

Map<Integer, Long> map = persons .stream() .collect( Collectors.groupingBy( Person::getAge, Collectors.counting() ) ) ;

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

Classe Collectors : partionningBy

Crée une Map<Boolean, …> avec un prédicat

map.get(TRUE) retourne la liste des personnes de plus de

20 ans

Map<Boolean, List<Person>> map = persons .stream() .collect( Collectors.partitioningBy(p -> p.getAge() > 20) ) ;

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)) ) ) ) ;

Classe Collectors : collectingAndThen

Collecte les données avec un downstream

Applique enfin une fonction appelée « finisher »

Indispensable pour retourner des collections immutables

Classe Collectors : collectingAndThen

Dans ce cas « Map::entrySet » est un finisher

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 ) ;

Quelques exemples réels

1er 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()) ;

1er 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()) ;

Un stream de movies

1er 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 d’une map

année / # de films

1er 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

1er 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()) ;

Et déterminer la plus

grande valeur

1er 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()) ;

Retourne l’année qui a

vu le plus grand

nombre de films

Conclusion

Conclusion

Pourquoi les lambdas ont-ils été introduits dans Java 8 ?

Conclusion

Pourquoi les lambdas ont-ils été introduits dans Java 8 ?

Réponse : parce que c’est à la mode !

Conclusion

Pourquoi les lambdas ont-ils été introduits dans Java 8 ?

Réponse : parce que c’est à la mode !

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 !

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

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

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++ ++ +++++++++++ +++++++++++++++++++++++++++++++++++ +++++++ ++++++++++++++++++++++++++++++++++++ +++++++ +++++++++++++++++++++++++++++++++++ ++++++++ ++++++++++++++++++++++++++++++++++ +++++++ +++++++++++++++++++++++++++++++++ +++++ +++++++++++++++++++++++++++++++++ ++++++ +++++++++++++++++++++++ + +++++ ++++++ +++++++++++++++++++++++ ++ ++++++ ++++++++++++++++++++++ + ++++++ ++++++++++++++++++++++ + ++++++ ++++++++++++++++++++ + + +++++++ ++++++ ++++++++ ++++++++++++++++++++ + + +++++++ ++++++++++++++++++++++ + ++++++ ++++++++++++++++++++++ + ++++++ +++++++++++++++++++++++ ++ ++++++ +++++++++++++++++++++++ + +++++ ++++++ +++++++++++++++++++++++++++++++++ ++++++ +++++++++++++++++++++++++++++++++ +++++ ++++++++++++++++++++++++++++++++++ +++++++ +++++++++++++++++++++++++++++++++++ ++++++++ ++++++++++++++++++++++++++++++++++++ +++++++ +++++++++++++++++++++++++++++++++++ +++++++ ++++++++++++++++++++++++++++++++++++ ++ +++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Plus c’est court, plus c’est bon !

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 !

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

Conclusion

Java 8 arrive, il s’agit de la mise à jour la plus importante

depuis 15 ans que Java existe

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 programmer

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 programmer

- convaincre nos boss…

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

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

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

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 !

top related