CSC4102 : Programmation orient e objet en JAVA - Lambda expressions, Streams, Optional Revision : 4084
←
→
Transcription du contenu de la page
Si votre navigateur ne rend pas la page correctement, lisez s'il vous plaît le contenu de la page ci-dessous
CSC4102 : Programmation orienté objet en JAVA — Lambda expressions, Streams, Optional Revision : 4084 Denis Conan Janvier 2019
Sommaire 1. Motivations et objectifs 2. Lambda expressions JAVA 3. Utilisation dans la manipulation des collections : les Streams 4. Utilisation dans la gestion des références null : Optional 5. Streams + Optional 6. Mise en pratique en TP (2h) + HP (3h) . Certaines sections de cette présentation sont insérées pour complétude dans l’optique de servir de référence (pour des approfondissements ultérieurs). Ces sections sont marquées d’un astérisque (« * ») dans leur titre. 2/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
1 Motivations et objectifs 1.1 Qui demande ? 1.2 Pourquoi des fonctions ? 1.3 Pourquoi des lambda expressions JAVA ?* 1.4 Quels sont les objectifs de la séance ? 3/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
1.1 Qui demande ? Brian Goetz, Java Language Architect, Oracle Specification lead for JSR-335 (Lambda Expressions for the Java Programming Language) Foreword of [Naftalin, 2015] Héritage et type paramétré = abstractions pour les données et les méthodes Lambda expression = abstraction pour les fonctions • 2009 : lancement du projet Lambda • 2006 : propositions pour ajouter le concept de « clôture »/« fermeture » (closure) • 1997 : ajout du concept de « classe anonyme » (anonymous inner class) • 1941 : travaux d’Alonzo Church sur la théorie du calcul, d’où vient la terminologie Ce sont les premiers pas vers la programmation orientée fonction, appelée aussi programmation fonctionnelle 4/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
1.2 Pourquoi des fonctions ? Exemple 1 : pour programmer des réactions à des événements d’IHM myComboBox.setOnAction((event) -> { Person selectedPerson = myComboBox.getSelectionModel() .getSelectedItem(); outputTextArea.appendText("ComboBox Action (selected: " + selectedPerson.toString()); + ")\n"); }); Exemple 2 : pour parallèliser des actions sur des grandes collections de donnnées fork double maxDistance = pointList.parallelStream() .map(p -> p.distance(0, 0)) fork fork .reduce(Double::max) map map map map .orElse(0.0); join fonctions join reduce reduce join reduce 5/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
1.3. Pourquoi des lambda expressions JAVA ?* I Voici un scénario extrait de [Naftalin, 2015] pour expliquer l’intérêt des lambda expressions 1. Commençons avec un code qui itère sur une collection d’objets modifiables List pointList = Arrays.asList(new Point(1,2), new Point(2,4)); for(Point p : pointList) { p.translate(1,1); } La ligne 2 de ce code est traduit par le compilateur en le code qui suit : Iterator pointItr = pointList.iterator(); while (pointItr.hasNext()) { ((Point) pointItr.next()).translate(1,1); } Problème : le compilateur génère du code qui sera toujours séquentiel Nous souhaiterions une exécution qui puisse être parallèle à la demande, c’est-à-dire selon la mise en œuvre de la méthode forEach D’où, nous souhaitons quelque chose comme ceci : List pointList = Arrays.asList(new Point(1,2), new Point(2,4)); pointList.forEach(/*translation d’un point selon le vecteur (1,1)*/); 6/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
1.3. Pourquoi des lambda expressions JAVA ?* II 2. Solution (avec le patron de conception « Commande ») // cette interface est définie dans java.lang (présentation simplifiée ici) public interface Iterable { ... void forEach(Consumer
1.3. Pourquoi des lambda expressions JAVA ?* III 3. Remplacement de la classe anonyme par une lambda expression 3.1 Le compilateur devrait pouvoir inférer l’interface attendu par forEach pointList.forEach(new Consumer() { public void accept(Point p) { p.translate(1,1); } }); 3.2 Avec le nom de l’interface attendue, le compilateur devrait pouvoir inférer le nom de la méthode pointList.forEach( Point public void accept(Point p) { p.translate(1,1); } ); 3.3 Le type de l’argument peut alors être inféré du type appelant (pointList) pointList.forEach( Point Point p p.translate(1,1) ); 3.4 Avec une syntaxe particulière (->), cela donne une lambda expression pointList.forEach(p -> p.translate(1, 1)); 8/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
1.4 Quels sont les objectifs de la séance ? Concept de lambda expression en JAVA Utilisation dans la manipulation des collections avec les Streams JAVA Utilisation dans la gestion des références null : Optional Ce sont les premiers pas vers la programmation orientée fonction (dite aussi programmation fonctionnelle) Mais nous n’étudions pas la programmation orientée fonction à proprement parler dans CSC4102 • Pour découvrir ce sujet, p.ex. : P.-Y. Saumont, Functional Programming in Java—How functional techniques improve your Java programs, [Saumont, 2017] 9/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
2 Lambda expressions JAVA 2.1 Définition et syntaxe 2.2 Exemples de lambda expressions 2.3 Contexte d’exécution d’une lambda expression 2.4 Référence de méthode* 2.5 Interfaces fonctionnelles standard* . Les éléments de cette section sont extraits des références [Naftalin, M., 2012], [Naftalin, 2015], et [Bloch, 2018] 10/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
2.1 Définition et syntaxe Lambda expression = conceptuellement une fonction • Arguments en entrée et valeur de retour Syntaxe • Soit : (parameters) -> expression • Soit : (parameters) -> { statements; } C’est un objet = une instance d’une interface (fonctionnelle1 ) Runnable r = () -> {}; // crée une lambda expression // et affecte une référence vers cette lambda expression à r Object o = r; // transtypage vers le haut, comme pour une référence/un objet Une lambda peut être utilisée là où une interface fonctionnelle est déclarée • Interface fonctionnelle =⇒ une méthode =⇒ pas d’ambiguı̈té • P.ex., « public void forEach(Consumer
2.2 Exemples de lambda expressions 1 (int x, int y) -> x + y // retourne la somme des deux arguments 2 (x, y) -> x - y // retourne la différence 3 () -> 42 // pas d’argument, retourne 42 4 (String s) -> System.out.println(s) // affiche l’argument et ne retourne rien 5 x -> 2 * x // un argument (sans parenthèses) 6 c -> { int s = c.size(); c.clear(); return s; } // type de l’argument possédant les méthodes size() et clear(), p.ex. une collection Types des arguments explicites (1, 4) ou inférés (2, 5, 6) • Pas de mélange entre « explicite » et « inféré » Le corps peut être un bloc (6) ou une expression (1–5) • Bloc retournant une valeur (dit value-compatible) ou rien (dit void-compatible) • Idem pour l’expression : une valeur (1, 2, 3, 5) ou rien (4) Un seul argument =⇒ possible d’omettre les parenthèses 12/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
2.3 Contexte d’exécution d’une lambda expres- sion Une lambda expression s’exécute dans un contexte • Comme attribut de classe : class Bar { Foo foo = () -> 42; } • Comme variable locale : void bar() { Foo foo = () -> 42; }; Elle peut utiliser les variables de son contexte, y compris « this » lorsqu’elle est imbriquée dans une instance de classe Règles classiques d’utilisation et de nommage des éléments du contexte • Un exemple légal : l’argument « i » de la lambda cache l’attribut « i » − class Bar { int i; Foo foo = i -> i * 2; }; • Un exemple illégal : « i » est déjà une variable locale de « bar » − void bar() { int i; Foo foo = i -> i * 2; }; 13/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
2.4 Référence de méthode* Cas particulier d’une lambda expression avec un seul argument + l’expression est un unique appel à une méthode avec un seul argument • Par exemple : str -> Integer.parseInt(str) • Simplification de l’écriture avec la forme « référence de méthode » − Pour le même exemple : Integer::parseInt Plus généralement, une lambda expression peut être représentée par une méthode concrète d’une classe =⇒ Une référence de méthode est un raccourci d’écriture d’une lambda expression avec un argument et l’expression formée de l’appel unique de la méthode référencée Syntaxe (plus d’informations dans la diapositive suivante) • ReferenceType::Identifier pour méthode de classe, p.ex. « Integer::parseInt » • ObjectReference::Identifier pour méthode d’instance : p.ex. « System.out::println » • ReferenceType::new pour constructeur, p.ex. « ArrayList::new » 14/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
2.4.1 Différents types de réf. de méthodes* Forme Forme Type de « lambda expression » « référence de méthode » méthode de référence 1 str -> Integer.parseInt(str) Integer::parseInt Statique 2 Instant then = Instant.now(); Instant.now()::isAfter Lié t -> then.isAfter(t) (bound) 3 str -> str.toLowerCase() String::toLowerCase Libre (unbound) 4 () -> new TreeMap TreeMap::new Constructeur de classe 5 len -> int[len] int[]::new Constructeur de tableau Les références de méthodes sont encore plus succinctes que les lamdda • Des outils comme SpotBugs et SonarLint dans Eclipse proposent systématiquement les remplacements • Lors du remplacement de la lambda par la référence, attention aux références de méthodes avec variables liées (#2) : variable « then » définie avant la définition de la lambda expression Versus méthode « Instant.now() » appelée dans la définition de la référence de méthode (cf. un exemple dans la diapositive qui suit) 15/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
2.4.2 Exemples de références de méthodes Classe seance8/LambdaVersusReferenceDeMethode 1 ToIntFunction < String > f1L = str -> Integer . parseInt ( str ) ; 2 ToIntFunction < String > f1RM = Integer :: parseInt ; 3 // essai de f1L et f1RM 4 System . out . println ( f1L . applyAsInt ( " 1 " ) + " ␣ " + f1RM . applyAsInt ( " 1 " ) ) ; 5 Instant then = Instant . now () ; 6 Predicate < Instant > f2L = t -> then . isAfter ( t ) ; 7 Predicate < Instant > f2RM = Instant . now () :: isAfter ; 8 // essai de f2L et f2RM + idem en lambdas 9 System . out . println ( f2L . test ( then ) + " ␣ " + f2RM . test ( then ) ) ; 10 System . out . println ( then . isAfter ( then ) + " ␣ " + Instant . now () . isAfter ( then ) ) ; 11 UnaryOperator < String > f3L = str -> str . toLowerCase () ; 12 UnaryOperator < String > f3RM = String :: toLowerCase ; 13 // essai f3L et f3RM 14 System . out . println ( f3L . apply ( " ABC " ) + " ␣ " + f3RM . apply ( " ABC " ) ) ; 15 Supplier < TreeMap < String , String > > f4L = () -> new TreeMap < String , String >() ; 16 Supplier < TreeMap < String , String > > f4RM = TreeMap :: new ; 17 // essai f4L et f4RM 18 System . out . println ( f4L . get () + " ␣ " + f4RM . get () ) ; 19 IntFunction < int [] > f5L = len -> new int [ len ]; 20 IntFunction < int [] > f5RM = int []:: new ; 21 // essaie f5L et f5RM 22 System . out . println ( f5L . apply (4) . length + " ␣ " + f5RM . apply (4) . length ) ; Affichage : « 1 1 », puis « false true », « false true », « abc abc », « {} {} », et enfin « 4 4 » 16/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
2.5 Interfaces fonctionnelles standard* java.util.function [PackageJavaUtilFunction, JSE8] • Plus de 40 fonctions − Dont 6 de base : Operator, Predicate, Function, Supplier, Consumer − Avec des variantes pour les types primitifs Interface Signature Exemple la fonction 1 UnaryOperator T apply(T t) String::toLowerCase 2 BinaryOperator T apply(T t1, T, t2) BigInteger::add 3 Predicate boolean test(T t) Collection::isEmpty 4 Function R apply(T t) Arrays::asList 5 Supplier T get() Instant::now 6 Consumer void accept(T t) System.out::println 7 ToIntFunction int applyAsInt(T t) Integer::parseInt 8 IntFunction R apply(int i) int[]::new 17/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
2.5.1 Exemples d’utilisation Classe seance8/InterfaceFonctionnelleStandard 1 // interfaces fon ction nelles de base 2 UnaryOperator < String > f1 = String :: toLowerCase ; 3 System . out . println ( f1 . apply ( " ABC " ) ) ; // 4 BinaryOperator < BigInteger > f2 = BigInteger :: add ; 5 BigInteger a = new BigInteger ( " 2 " ) , b = new BigInteger ( " 3 " ) ; 6 System . out . println ( f2 . apply (a , b ) ) ; // 7 Predicate < Collection < String > > f3 = Collection < String >:: isEmpty ; 8 System . out . println ( f3 . test ( new ArrayList < String >() ) ) ; // 9 Function < String [] , List < String > > f4 = Arrays :: asList ; 10 System . out . println ( f4 . apply ( new String [] { " a " , " b " }) ) ; // 11 Supplier < Instant > f5 = Instant :: now ; 12 System . out . println ( f5 . get () ) ; // 13 Consumer < String > f6 = System . out :: println ; 14 // variante pour les types primitifs 15 ToIntFunction < String > f7 = Integer :: parseInt ; 16 IntFunction < int [] > f8 = int []:: new ; 17 f6 . accept ( " abc " ) ; System . out . println ( f7 . applyAsInt ( " 11 " ) ) ; // Affichage : « abc », puis « 5 », « true », « [a, b] », « 2019-03-13T21:06:26.529Z », « abc », « 11 », et enfin « 4 » 18/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3 Utilisation dans la manipulation des collections : les Streams 3.1 Retour sur les motivations et les objectifs des lambda expressions 3.2 Pipeline, stream, et évaluation tardive 3.3 Début du pipeline 3.4 Traitements intermédiaires du pipeline 3.5 Terminaison d’un pipeline 3.6 Quelques exemples . Les éléments de cette section sont extraits des références [Naftalin, M., 2012], [Naftalin, 2015], et [Bloch, 2018] 19/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.1 Retour sur les motivations et les objectifs des lambda expressions Motivation 1 : introduire des élements de programmation orientés fonction Motivation 2 : raccourcir l’écriture avec le nouveau sucre syntaxique « -> » Motivation 3 : permettre des exécutions parallèles • C’est cette motivation qui donne la bibliothèque des Streams Imaginons une collection de grande taille ET un ordinateur avec bcp de cœurs − Mise en œuvre du paradigme de programmation « Map/Reduce » fork double maxDistance = pointList.parallelStream() fork fork .map(p -> p.distance(0, 0)) map map map map .reduce(Double::max) join join .orElse(0.0); reduce reduce join reduce 20/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.2 Pipeline, stream, et évaluation tardive Philosophie UNIX : « At its heart is the idea that the power of a system comes more from the relationships among programs than from the programs themselves. Many UNIX programs do quite trivial things in isolation, but, combined with other programs, become general and useful tools. » [Kernighan and Pike, 1984] • Les lambda expressions = traitements à granularité fine et composables Stream = Séquence de valeurs, ordonnées ou non, et non stockées Pipeline de streams + évaluation tardive • C’est l’opération terminale qui « tire » les valeurs − Aucune valeur n’est calculée tant qu’elle n’est pas demandée IntStream.iterate(1, i -> i * 2) // génération stream de taille infinie .limit(10) // limitation du nombre de générations .forEach(System.out::println) // opération qui « tire » les valeurs Toutes les valeurs ne sont peut-être pas calculées P.ex., IntStream.iterate(1, i -> i * 2).limit(10).anyMatch(v -> v == 128) 21/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.3 Début du pipeline IntStream, etc. pour éviter les opérations de conversion entre le type conteneur Integer et le type primitif int + méthodes generate, iterate, range, of, etc. pour démarrer le pipeline • P.ex., IntStream.iterate(1, i -> i * 2), IntStream.range(7, 42) Interface Collection maintenant avec les méthodes stream et parallelStream qui créent un stream • P.ex., maListe.stream() • Dans CSC4102, nous n’utiliserons pas de parallélisation Plus rarement, classe Stream avec les méthodes de classe of, empty, etc. • P.ex., Stream.of(new Point(1, 2), new Point(2, 4)) 22/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.4 Traitements intermédiaires du pipeline Transformation = retourne un stream d’un autre type d’éléments • Stream map(Function mapper) et flatMap • DoubleStream mapToDouble(ToDoubleFunction mapper), etc. Filtrage = Stream filter(Predicate predicate), dropWhile Combinaison de streams : concat Tri (sorted), suppression des doublons (distinct), troncature (limit et skip) Introspection pour le déverminage = même séquence en sortie + instertion d’un traitement avec peek IntStream.iterate(1, i -> i*2).limit(10).peek(System.out::println).anyMatch(v -> v==128); 23/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.5 Terminaison d’un pipeline Recherche d’un élément et test de présence : • Optional findAny2 , findFirst • boolean anyMatch(Predicate), allMatch, et noneMatch Réduction : • Calcul : int sum(), Optional min(), Optional max(), long count(), Optional average(), IntSummaryStatistics summaryStatistics • Collecte : R collect(Collector
3.6 Quelques exemples I Début : génération d’une séquence infinie avec IntStream Intermédiaire : troncature avec limit + insertion déverminage avec peek Terminaison : test de présence avec anyMatch Démonstration de l’évaluation tardive Classe seance8/lambdaexpressions/ExampleNaftalin2015 1 IntStream . iterate (1 , i -> i * 2) 2 . limit (10) 3 . peek ( System . out :: println ) 4 . anyMatch ( v -> v == 16) ; Affichage : 1 2 4 8 16 25/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.6 Quelques exemples II Début : Collection::stream Intermédiaire : filtrage avec filter Terminaison : recherche d’un élément avec findFirst Recherche dans une collection avec filtrage + obtention de la première valeur trouvée • Hypothèse : la précondition du cas d’utilisation « ajouter un genre » inclut la condition « genre avec ce nom inexistant » Classe etudesdecas/mediathequeaveclambdasoptionaletstreams/Mediatheque 1 private Optional < Genre > chercherGenre ( final String nomGenre ) { 2 return lesGenres . stream () . filter ( g -> g . getNom () . equals ( nomGenre ) ) . findFirst () ; 3 } 4 5 /* * 6 * sup prime rGenr e permet de supprimer un Genre dans la liste des genres . La classe Optional est étudiée dans la section qui suit celle-ci. 26/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.6 Quelques exemples III Terminaison : test de présence avec anyMatch Existe-t-il un document avec le genre donné ? Classe etudesdecas/mediathequeaveclambdasoptionaletstreams/Mediatheque 1 } 2 3 /* * 27/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.6 Quelques exemples IV Début : génération d’une séquence infinie avec IntStream Intermédiaire : troncature avec limit + transformation avec mapToObj Terminaison : réduction avec collect en une liste avec Collectors::toList Collecte dans une liste Classe seance8/lambdaexpressions/ExampleNaftalin2015 1 List < String > l = IntStream . iterate (1 , i -> i * 2) 2 . limit (10) 3 . mapToObj ( String :: valueOf ) 4 . collect ( Collectors . toList () ) ; 5 System . out . println ( l ) ; Affichage : [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] Cet exemple démontre implicitement une construction d’objet • On pourrait écrire mapToObj(Integer::new) pour récupérer une List 28/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.6 Quelques exemples V Collecte dans une chaı̂ne de caractères avec Collectors::joining Classe seance8/lambdaexpressions/ExampleNaftalin2015 1 String s = IntStream . iterate (1 , i -> i * 2) 2 . limit (10) 3 . mapToObj ( String :: valueOf ) 4 . collect ( Collectors . joining ( " ␣ + ␣ " ) ) ; 5 System . out . println ( s ) ; Affichage : 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 + 512 29/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.6 Quelques exemples VI Début : génération de deux streams Intermédiaire : concaténation des deux streams avec Stream::concat + retrait des doublons avec distinct Terminaison : collecte dans un ensemble avec toSet Démonstration de la concaténation de streams Classe seance8/lambdaexpressions/ExampleNaftalin2015 1 List < List < String > > listeDeListes = new ArrayList < >() ; 2 listeDeListes . add ( Arrays . asList ( " a " , " b " , " c " ) ) ; 3 listeDeListes . add ( Arrays . asList ( " c " , " d " , " a " ) ) ; 4 Set < String > concat = Stream . concat ( listeDeListes . get (0) . stream () , 5 listeDeListes . get (1) . stream () ) 6 . distinct () 7 . collect ( Collectors . toSet () ) ; 8 System . out . println ( concat ) ; Affichage : [a, b, c, d] 30/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.6 Quelques exemples VII Même exemple avec généralisation à un nombre quelconque de listes • Début : obtention de la séquence de listes de la liste englobante Intermédiaire : transformation de chaque liste de String en une stream avec List::stream et insertion dans une séquence de String − flatMap = flattened map • Retourne un Stream au lieu d’un Stream Classe seance8/lambdaexpressions/ExampleNaftalin2015 1 List < List < String > > listeDeListes = new ArrayList < >() ; 2 listeDeListes . add ( Arrays . asList ( " a " , " b " , " c " ) ) ; 3 listeDeListes . add ( Arrays . asList ( " c " , " d " , " a " ) ) ; 4 Set < String > flatMap = listeDeListes . stream () 5 . flatMap ( List :: stream ) 6 . distinct () 7 . collect ( Collectors . toSet () ) ; 8 System . out . println ( flatMap ) ; 31/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
3.6 Quelques exemples VIII Début : génération d’un streams Terminaison : application d’un effet de bord avec forEach Exemple d’application d’un effet de bord Classe seance8/lambdaexpressions/ExampleNaftalin2015 1 List < Point > points = Arrays . asList ( new Point (1 , 2) , new Point (2 , 4) ) ; 2 System . out . println ( points ) ; 3 points . stream () . forEach ( p -> p . translate (1 , 2) ) ; 4 System . out . println ( points ) ; Affichage : [java.awt.Point[x=1,y=2], java.awt.Point[x=2,y=4]] [java.awt.Point[x=2,y=4], java.awt.Point[x=3,y=6]] 32/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
4 Utilisation dans la gestion des références null : Optional 4.1 Problème des références null 4.2 Solution non satisfaisante : documentation 4.3 Solution/idiome JAVA : classe Optional 33/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
4.1 Problème des références null Situations dans lesquelles « pointeur null » signifie « absence de valeur » • P.ex. valeur de retour pouvant être null SousGenre Classe seance8/lambdaoptional/DocumentSansOptional nom:string nbEmprunts:integer=0 1 public String ge t No m So u sG enre () { 0..1 possède 2 String result = " inconnu " ; 3 if ( genre != null ) { // attribut pouvant etre null 4 SousGenre sg = genre . getSousGenre () ; // peut retourner null 5 if ( sg != null ) { Genre nom:string 6 String n = sg . getNom () ; nbEmprunts:integer=0 7 if ( n != null ) { // inutile si verifie dans l ’ invariant correspond 8 result = n ; 0..1 9 } 10 } * 11 } Document 12 return result ; code:string 13 } titre:string auteur:string getSousGenre() 34/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
4.2 Solution non satisfaisante : documentation Besoin d’avertir le programmeur de la méthode appelante • Premier essai : utiliser la documentation Javadoc Classe seance8/lambdaoptional/GenreSansOptional 1 /* * 2 * obtient le sous - genre . 3 * 4 * @return le sous - genre , qui peut etre { @code null }. 5 */ 6 public SousGenre getSousGenre () { 7 return sousGenre ; 8 } 35/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
4.3 Solution/idiome JAVA : classe Optional La solution/l’alertidiome JAVA : • Similairement à d’autres langages comme Groovy ou Scala, JAVA propose depuis la version 8 un idiome pour rendre explicite la possibilité qu’une référence puisse être null et ainsi alerter le programmeur pour qu’il evite le déréférencement dans ce cas − P.ex. : Optional getSousGenre() • Pour votre culture générale, conférence de C.A.R. Hoare, qui en 1965 a introduit le concept de « référence null », ce qu’il considère maintenant comme une erreur : − « Null References: The Billion Dollar Mistake » https://www.infoq.com/presentations/ Null-References-The-Billion-Dollar-Mistake-Tony-Hoare 36/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
4.3.1 Classe Optional Classe conteneur et paramétrée par le type du contenu : Optional Pas de constructeur, mais création par méthodes de classes dite « fabriques » (factories) • Optional g = Optional.of(genre) : genre = null =⇒ exception NullPointerException • Optional g = Optional.ofNullable(genre) : valeur null possible • Optional g = Optional.empty() : contient null Des méthodes pour récupérer la valeur ou tester si null • g.get() : lève l’exception NoSuchElementException si contenu null • g.isPresent() : retourne false si contenu null • g.orElse("inconnu") : retourne contenu non null, sinon "inconnu" Des méthodes pour manipuler la valeur si non null • sg.map(SousGenre::getNom) : applique la lambda expression si non null et retourne la valeur dans un Optional (p.ex. ici, un Optional) • sg.flatMap(SousGenre::getNom) : idem map, mais sans mettre le résultat dans un Optional (p.ex. ici retourne un String) 37/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
4.3.2 Où mettre Optional ? « Optional is primarily intended for use as a method return type where there is a clear need to represent ”no result,” and where using null is likely to cause errors. » [Class Optional, JSE8] « The intention of the Optional class is not to replace every single null reference » [Urma, 2014] Dans le module CSC4102, Optional en guise de valeur de retour lorsque null est fonctionnellement possible (par un cas d’erreur) Classe seance8/lambdaoptional/GenreAvecOptional 1 /* * 2 * obtient le sous - genre . 3 * 4 * Le diagramme de classes specifie qu ’ un sous - genre est optionnel . 5 * 6 * @return le sous - genre , qui peut etre { @code null }. 7 */ 8 public Optional < SousGenre > getSousGenre () { 9 return Optional . ofNullable ( sousGenre ) ; 10 } 38/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
4.3.3 Manipulation d’un objet Optional (1/2) Même exemple qu’au début de la section avec Même structure : imbrication de if • isPresent : retourne true si un objet est présent • get : retourne la valeur présente, sinon lève l’exception NoSuchElementException Classe seance8/lambdaoptional/DocumentAvecOptional1 1 public String ge t No m So u sG enre () { 2 String result = " inconnu " ; 3 if ( getGenre () . isPresent () ) { 4 Optional < SousGenre > sg = genre . getSousGenre () ; 5 if ( sg . isPresent () ) { 6 String n = sg . get () . getNom () ; 7 if ( n != null ) { // inutile si dans l ’ invariant 8 result = n ; 9 } 10 } 11 } 12 return result ; 13 } 39/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
4.3.4 Manipulation d’un objet de Optional (2/2) Utilisation de flatMap, map, et orElse • GenreAvecOptional::getSousGenre retourne un Optional • D’où, getGenre().map(GenreAvecOptional::getSousGenre) retournerait un objet de type Optional • Donc, flatMap pour obtenir un objet de type Optional au lieu d’un objet de type Optional − flatMap : This method is similar to map(Function), but the mapping function is one whose result is already an Optional, and if invoked, flatMap does not wrap it within an additional Optional [Class Optional, JSE8] • orElse : retourne la valeur si présente (≡ get), sinon une valeur par défaut Classe seance8/lambdaoptional/DocumentAvecOptional2 1 public String ge t No m So u sG enre () { 2 return getGenre () 3 . flatMap ( G e n r e A v ec Opt io nal :: getSousGenre ) 4 . map ( SousGenre :: getNom ) 5 . orElse ( " inconnu " ) ; 6 } 40/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
5 Streams + Optional Règle 45 de « Effective JAVA » [Bloch, 2018] : « Use streams judiciously » • « Overusing streams makes programs hard to read and maintain » Classe etudesdecas/mediathequeaveclambdasoptionaletstreams/Mediatheque 1 . stream () . filter ( 2 f -> f . correspond ( 3 lesClients . values () . stream () 4 . 41/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
6 Mise en pratique en TP (2h) + HP (3h) Utilisation des Streams et de Optional dans certaines parties du code Continuation du développement de l’application de l’étude de cas Organisation du TP (2h) • 1h : Travail en binôme-projet • 2 × 5mn : Évaluation croisée entre binômes-projets + bilan intermédiaire collectif • Reste de la séance : continuation des travaux Rendu de la séance en HP (3h) : PDF + JAVA, avec « seance8 » dans « sprint2 » 42/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
Références I Bloch, J. (2018). Effective Java, 3nd Edition. Addison-Wesley. Class Collectors (JSE8). Javadoc of the class java.util.stream.Collectors of JAVA 9. https: //docs.oracle.com/javase/9/docs/api/java/util/stream/Collectors.html. Class Optional (JSE8). Javadoc of the class java.util.Optional of JAVA 9. https: //docs.oracle.com/javase/9/docs/api/index.html?java/util/Optional.html. Kernighan, B. and Pike, R., editors (1984). The UNIX Programming Environment. Prentice-Hall. 43/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
Références II Naftalin, M. (2015). Mastering Lambdas : Java Programming in a Multicore World. Mc Graw Hill, Oracle Press. Naftalin, M. (2012). The Lambda FAQ. http://www.lambdafaq.org/. PackageJavaUtilFunction (JSE8). Javadoc of the package java.util.function of JAVA SE 8. https://docs.oracle.com/javase/9/docs/api/java/util/function/ package-summary.html. Saumont, P.-Y. (2017). Functional Programming in Java—How functional techniques improve your Java programs. Manning. 44/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
Références III Urma, R.-G. (2014). Tired of Null Pointer Exceptions ? Consider Using Java SE 8’s Optional ! https: //www.oracle.com/technetwork/articles/java/java8-optional-2175753.html. 45/45 01/2019 Denis Conan CSC4102 : Lambda expressions, Streams et Optional
Vous pouvez aussi lire