Retour d'expérience – Les streams et les lambdas ont changé ma manière de programmer ! – trier une liste



Retour d'expérience – Les streams et les lambdas ont changé ma manière de programmer ! – trier une liste

0 0


ReXStreamLambda

REX sur les Stream et les Lambda pour Extia (07/04/2016)

On Github BloumineX / ReXStreamLambda

Retour d'expérience

Les streams et les lambdas ont changé ma manière de programmer !

Created by Xavier BLOUMINE / @bloumix

Who Am I ?

Développeur Java 6 Ans xp Passionné par le Clean Code et les best practices ! (TDD/BDD/DDD/Craftman/conférences/...)

En mission chez Natixis crédit consommation Contexte : JBoss EAP 6.4 / Java 8 / JPA / Rest / ...

Au Programme

  • Pourquoi ce talk ?
  • Retro sur la JSR 337 : Java 8
  • Comment Java 8 améliore la lisibilité de notre code ?
    • Evite d'implémenter des interfaces polluantes
    • Simplification sur le traitement des collections
    • Collection : Boucle for ? forEach ?
  • Comment ça fonctionne alors ?!
    • Les interfaces fonctionnelles
    • Les Lambdas
    • Les streams()
  • (Bonus) Quelques exemples avec le REPL (Java 9)

Pourquoi ce talk ?

Sondage qui a déjà pratiqué Java 8 ? Plupart des gens utilisent java 6 voire java 7 :((( Promouvoir la curiosité :) et s'amuser !!!

JSR 337 : Java 8

  • Interface @Default
  • Interface Fonctionnelle
  • JavaFX
  • Java Time
  • Lambda
  • Security
  • Stream
  • plus d'infos sur le site d'Oracle
-> security : ajout TLS1.2 par defaut / Kerberos inclus / KeyStore enhencement... -> Java FX : possibilité d'embarquer du Swing dans FX, support html5'

Comment Java 8 améliore la lisibilité de notre code

Démonstration par l'exemple

trier une liste

Objectif : trier une liste à l'aide d'un Comparator

Java 7
Comparator<String> c = new comparator<String>() {
    @Override
    public boolean compare(String s1, String s2) {
        return s1.compageIgnoreCase(s2);
    } 
}
/// stuff there ///
list.sort(c);
Utilisation d'une classe anonyme pour pouvoir utiliser le Comparator Résultat : Rend le code moins lisible en devant réimplémenter la classe Comparator (cas simple)...

Java 8

list.sort((s1, s2) -> s1.compareIgnoreCase(s2));
Utilisation d'une Lambda

Traitement un peu plus complexe sur les collections

Objectif :

  • Enlever d'une liste les éléments impairs
  • Multiplier par deux les éléments restants
  • Enlever les doublons
  • Additionner les valeurs restantes

Java 7

//On ne peux pas modifier une liste sur lequel on iterer
// donc on la copie dans une nouvelle liste
List< Integer> listResult = new ArrayList< >(list);
                            
//enlever les éléments impairs
for (Integer i : list) {
    if (i%2 != 0)
        listResult.remove(i);
}
//enlever les doublons
Set< Integer> setInte = new HashSet<>(listResult);
//Multiplier par 2 les élements et renvoyer la somme
Integer result = 0;
for (Integer i : setInte) {
    result = result + i * 2;
}
                        

OMG

regrouper multiplier par 2 et result pour eviter une nouvelle iteration

Java 8

On va simplifier un peu le code ...
list.stream()
    .filter((value -> value % 2 != 0))
    .map(value -> value * 2)
    .distinct()
    .reduce(0, (val1, val2) -> val1 + val2)
                         

Boucle for/ForEach sur les collections ?

Rien de plus facile !

//le for n'a pas de d'équivalent pur car il est trop générique
//les streams peuvent parcourir les éléments facilement et faire des opérations dessus.
list.stream()
    .filter((value -> value % 2 != 0))
    .map(value -> value * 2)
    .distinct()
    .forEach(value -> System::println) //forEach ;)
                         

Mais Comment ça fonctionne ?!?

Un peu d'histoire : Dans certains langages tels que les langages fonctionnels (scala / haskell / ...) ou encore le JS, il est possible de passer des fonctions en tant que paramètres. En Java, tout est objet ! On ne peut pas passer des fonctions car ça n'existe pas ...

les Interfaces fonctionnelles

Interface ne possédant qu'une seule méthode abstraite

Peut avoir n méthodes implémentées mais uniquement une méthode abstraite Sert de conteneur pour la méthode abstraite

L'Annotation @FunctionnalInterface indique au compilateur que cette interface est une interface Fonctionnelle

                            
@FunctionnalInterface
public Interface StringComparator {
    default int methodFolle(String a) { /* Some stuff there */}
    int compare(String a, String b);
}

La liste des interfaces fonctionnelles disponibles de l'api

Equivalent en C++ : functeur pointeur de fonction Le principe : encapsuler la methode abstraite au sein d'un objet java

exemple

Contexte : on voudrait afficher chaque valeur d'une liste grâce à la méthode forEach Signature de la méthode : Implémentation
Consumer< Integer> consumer = new Consumer< Integer>() {
    @Override
    public void accept(Integer integer) {
        System.out.println(integer);
    }
};
list.stream().forEach(consumer);
                        
Nous passons un objet et non une méthode !

Première solution possible

Passer la signature de la méthode qui sera interprété comme un Consumer !
public class PrinterList {
//static obligatoire car il n'y a pas d'implementation !
    public static void print(Integer i) { 
        System.out.println(i);
    }
}
//Le compilateur va avec cette nouvelle syntaxe comprendre que cette méthode 
//peut etre utiliser en tant que Consumer
list.stream().forEach(PrinterList::print);
//Possibilité aussi de passer par : 
list.stream().forEach(System.out::println); 
                        
Même si le compilateur effectue des transformations en interne, nous envoyons maintenant notre méthode au lieu d'un objet !Nous devons quand même créer une classe pour paramètrer notre fonction

Seconde solution possible

Passer par une fonction annonyme : une lambda La fonction anonyme doit respecter la signature de l'interface fonctionnelle
list.stream().forEach((value) -> System.out.println(value));
                        

Les Lambdas

Comme l'a montré le dernier exemple, la Lambda peut etre considérée comme une fonction anonyme. Attention ! Tout est objet en JAVA !! Le compilateur va se charger de creer une classe anonyme derrière !

La syntaxe

Différents Exemples

//value n'a pas de parametre mais le compilateur sais le reconnaitre
Arrays.<String>asList("tst1", "tst2").stream()
        .forEach(value -> System.out.println(value));

//Lambda expression et inférence du type des paramètres
Arrays.<String>asList("tst1", "tst2")
        .forEach((String value) -> System.out.println(value));

//block de données possible
Arrays.<String>asList("tst1", "tst2")
        .forEach(value -> {  System.out.println(value); });
        
//Plusieurs parametre (reduce), parenthèse Obligatoire
list.stream()
    .filter((value -> value % 2 != 0))
    .map(value ->  value * 2)
    .distinct()
    .reduce(0, (val1, val2) -> val1 + val2)
                    

Warning

Même si la lambda peut récuperer le scope du parent (principe de la closure)Elle ne peux pas modifier les éléments extérieurs !
public void test() {
    int x = 0; //variable locale
    Arrays.asList(1, 2, 3).forEach(value -> x+=value); //erreur !!!!
    //La variable x est considérée final par la lambda.
}
                        

Les Streams()

Qu'est ce ?

Un stream est un ensemble d'opérations sur des collections Il y a deux types d'opérations : Les Opérations intermédiaires N'effectue que des opérations de manipulation sur les collections Les opérations finales Un stream effectue des transformations jusqu'à une opération finale Lien vers l'api Java des streams

Les opérations intermédiaires

.filter(Predicate< E>) → Stream< E>

Choisit les élements si le predicat est vrai

.map(Function< E, R>) → Stream< R>

Transforme les élements

.flatMap(Function< E, Stream< R>>) → Stream< R>

Applatit l'ensemble des Streams de chaque élement

.skip(), .limit()

Saute des élements, reduit le nombre d'élement

.distinct(), .sorted(Comparator< E>)

Sans doublon (par rapport à equals), trie

Les opérations terminales

.count()

Compte les élements

.forEach(Consumer< E>)

Applique le consumer pour chaque élement

.allMatch(Predicate< E>), .anyMatch(Predicate< E>)

Vrai si tout les/au moins un élement(s) match

.findAny(), .findFirst() → Optional< E>

Trouver un ou le premier, renvoie une posibilité d'élement

.toArray(IntFunction)

Crée un tableau ex: .toArray(String[]::new)

.reduce(), collect(Collector) Aggège les éléments en 1 valeur (resp. non-mutable, mutable)

Un peu de cas pratique ?

Questions ?

1/27
Retour d'expérience Les streams et les lambdas ont changé ma manière de programmer ! Created by Xavier BLOUMINE / @bloumix