1. IS JAVA 8 A TRUE FUNCTIONAL PROGRAMMING LANGUAGE ?
- Samir Chekkal
- Saad El Khlifi
2. Plan
1. Programming Paradigm Overview
2. Imperative Pardigm Vs Functional Paradigm
3. Corner Stones of Functional Paradigm
1. Immutablity
2. Recursion
3. Functions as first class citizens
4. Higher order functions
5. Lazyness
4. Conclusion
3. 1.Programming Paradigm Overview
- A programming paradigm is a general approach, orientation, or philosophy
of programming that can be used when implementing a program.
- Main programming paradigms:
- Imperative programming: C, Java, Scala ...
- Functionnal programming: Haskell, ML, Scala, Java 8 (?) ...
- Logic programming: Prolog ...
- Object Oriented Programming: Java, Scala, ...
4. 2.Imperative Pardigm Vs Functional Paradigm
static List<String> approvisionnement() {
List<String> refs = new ArrayList<>();
int index = 0;
for(Product p : Store.catalog()) {
if(p.getCategory() == Product.Category.MOBILE_PHONE) {
refs.add(p.getRef());
index ++;
if(index > 10)
break;
}
}
Collections.sort(refs);
return refs;
}
5. 2.Imperative Pardigm Vs Functional Paradigm
static Map<Product.Category, List<Product>> categories() {
Map<Product.Category, List<Product>> result = new HashMap<>();
for(Product p : Store.catalog()) {
if(result.get(p.getCategory()) != null) {
result.get(p.getCategory()).add(p);
} else {
List<String> catProducts = new ArrayList<>();
catProducts.add(p);
result.put(p.getCategory(), catProducts);
}
}
return result;
}
8. 3. Corner Stones of Functional Paradigm
1. Immutablity
2. Recursion
3. Functions as first class citizens
4. Higher order functions
5. Lazyness
9. 3.1 Immutablity
- High level coding
- Mutability + Shared state = Time
bomb
- Parallelism
- But Threads API is complexe
- For free with streams! Really ?
10. static long badIdea() {
// Tables are not thread safe:
// Correctness ?
long[] acc = {0L};
LongStream
.rangeClosed(1, LIMIT)
.parallel()
.forEach( i -> acc[0] += i);
return acc[0];
}
static long lessBadIdea() {
// Thread safe , correct response
// Performance ?
AtomicLong acc = new AtomicLong(0L);
LongStream
.rangeClosed(1, LIMIT)
.parallel()
.forEach( i -> acc.addAndGet(i));
return acc.get();
}
static long sumByReduction() {
return LongStream
.rangeClosed(1, LIMIT)
.parallel()
.sum();
}
3.1 Immutablity
Result: 5000000050000000
in ~288 ms
Result: 5000000050000000
in ~8306 ms
Result: 1839299186097435
in ~342 ms
11. 3.2.Recursion
static long factorialImperative(final int n) {
long r = 1;
for (int i = 1; i <= n; i++) {
r *= i;
}
return r;
}
static long recFact(final long n) {
return (n == 1) ? 1 : n*recFact(n-1);
}
13. 3.2.Recursion
static long factorialTailRecursive(final long n) {
return factorialHelper(n, 1);
}
private static long factorialHelper(final long n, final long acc) {
return n == 1 ? acc : factorialHelper(n-1, n*acc);
}
20. 3.3.First class functions
java.util.function package to the rescue
• Predicate
• Consumer
• Supplier
• Function
• …
21. 3.4.Higher order functions
static int sumOfInts(int a, int b) {
return a > b ? 0 : a + sumOfInts(a+1, b);
}
static int sumOfCubes(int a, int b) {
return a > b ? 0 : a*a*a + sumOfCubes(a+1, b);
}
static int sumOfFact(int a, int b) {
return a > b ? 0 : fact(a) + sumOfFact(a+1, b)
}
22. 3.4.Higher order functions
static int higherSum(Function<Integer, Integer> f, int a, int b) {
return a > b ? 0 : f.apply(a) + higherSum(f, a + 1, b);
}
int sumOfSquare = higherSum(x->x*x, 1, 6);
int sumOfCube = higherSum(x->x*x*x, 5, 21);
int sumOfFactoriel = higherSum(x->fact(x), 13, 17);
…..
23. 3.4.Higher order functions
static BiFunction<Integer, Integer, Integer> curriedSum(Function<Integer, Integer> f) {
return
(a, b) -> { return a > b ? 0 : f.apply(a) + curriedSum(f).apply(a+1, b); };
}
// Curryed version call
int sumOfSquare = curriedSum(x->x*x).apply(1, 6);
int sumOfCube = curriedSum(x->x*x*x).apply(5, 21);
int sumOfFactoriel = curriedSum(x->fact(x)).apply(13, 17);
…..
28. References
- http://clojure.org/rationale Mutable stateful objects are the new spaghetti code
- Compositional Programming: http://www.cs.ox.ac.uk/research/pdt/ap/ssgp/slides/odersky.pdf
- http://www.info.ucl.ac.be/~pvr/paradigmsDIAGRAMeng108.pdf
- http://www.ibm.com/developerworks/aix/library/au-aix-multicore-multiprocessor/
Editor's Notes
Merci d’être venu pour assister à cette session
Merci à JMaghreb pour cette exellente occasion qu’on nous a donné à fin de nous permettre nous exprimer sur un des sujet qui nous est cher qui est la pro
…. Je m’appelle Saad El Khlif, .. Je suis Developpeur Java chez SQLi
Et voici mon collègue que je lui laisserais le soin de se présenter …..
Comme tout le monde sait aujourd’hui le langage Java avec sa nouvelle version Java 8 a apporté avec lui de nouvelle features, avec un des plus grand changement qu’a connu le langage depuis sa version 5, et cela avec comme objectif principale : Embrasser le monde fonctionnelle, tout le monde maintenant a sans doute entendu parler des Lambda expressions, des interfaces fonctionnelles, des streams...etc
Très bien .
Alors aujourd’hui nous allons vous parler des principaux concepts qui caractérisent la programmation fonctionnelle et surtout nous allons essayer de projeter Java 8 sur ces concepts et cela pour savoir à quel point Java 8, se considère comme langage fonctionnel, est ce que Java 8 est un vrai langage fonctionnel ? c’est ça la question que nous allons essayer à travers cette session analyser et répondre !
d’abord c’est quoi la programmation fonctionnelle ?
Qu’apporte ce paradigme de programmation en terme de mécanisme, d’idioms, capables de nous aider nous développeurs à construire des logiciels rebuste, flexible, facilement maintenable, et avec un grand niveau de reutilisabilité ?
Tout ça et autres font le menu de cette session !
Dans cette session nous allons dans un premier lui (lieu) rappeller les différents paradigmes de programmation
Pour cela nous allons revenir sur la définition de ce que s’est que un paradigme
après nous allons essyer d’enumerer l’ensemble des principaux paradigmes les plus influents et les plus utilisés aujourd’hui
Une fois c’est fait nous allons essayer d’attaquer le paradigme fonctionnelle avec les différent concepts et mécanismes qui les caractérise
Et à la fin nous allons conclure et laisser le temps pour les questions réponses
Comme vous le remarquez Java se situe un peu partout, il est considéré maintenant de plus en plus un langage polyvalent
A noter que Java avant la version d’aujourd’hui la 8, on pouvait faire avec de la programmation fonctionnelle. mais, et nous allons le constater ensemble,
il n’était pas adapté pour jouer ce rôle, c’est à dire, Java 7 et inférieur ne disposait pas des mécanismes forts pour le qualifier dans le rang des langages fonctionnelles
Vous allez voir que le paradigme fonctionnelle est un style complètement à part ce que nous avons l’habitude de faire dans nos développement de tous les jours. c’est un style qui pour l’adopter il faut non seulement une nouvelle façon de voir les choses mais aussi faut les mécanismes nécessaire capables de le rendre applicable.
Pour donner une définition claire du style de la programmation fonctionnelle, permettez nous de vous exposer un exemple de plain old java code
Par-contre nous allons demander à vous de le lire , l'expliquer et le qualifier,, et comme nous avons que peu temps, le challenge sera de le lire dans 10 seconde ?
…
…
Il s’agit d’une fonction qui sert à retourner la liste triée de 10 références de produits de type MOBILE_PHONE, l’objectif et d’approvisionner le stock
C’est un besoin qui comme vous venez de le constater facile à exprimer mais un peu complexe à coder, à lire, maintenir.. etc
Pour vous rapprocher encore plus de l’idée, permettez nous de vous donner un deuxième exemple, et vous demander encore une fois de le lire, l’expliquer et le qualifier.
Il s’agit d’une fonction ayant pour objectif de retourner le liste des produits par groupés par category (c’est comme une requête groupeBy de SQL que nous avons l’habitude de manipuler)
Encore une fois c’est une fonctionnalité facile à exprimer, mais, avec le code que nous avons l’habitude d'écrire est un code difficile à lire, bas niveau
Au lieu d’exprimer facilement notre besoin on est obliger d’exprimer le comment, les étapes à suivre pour arriver à ce que nous voulons
De plus avouons le c’est un code verbeux , nous avons beaucoup de variable à déclarer et a manipuler, des structure de contrôle
deux exemples
Les deux bout de code que nous venons de vous présenter représentent le paradigme impératif
un ensemble d’instruction de bas niveaux ayant pour objectif de basculer d’un état à un autre
mutabilité, side effect, itération avec des boucle sont les caractéristiques de la programmation impératif
Très bien
Maintenant nous péremétez nous de vous présenter un autre extrait de code et vous demander de le lire
N’essayez pas de vous vous perturber avec ce que ça veut dire le mot stream
Essayez de lire le code de la manière la plus naturelle possible, essayez le lire les verbes
Comme vous venez de le constater c’est un code qui ressemble exactement à l'énoncez du problème
Une seule ligne de code de haut niveau, concis, facile à lire, et à évoluer
Pas de variable et créer et à manipuler, pas de boucle à gérer,
Les verbe sont des fonctions
Vous allez nous dire , on connaît tous ce que c’est que une fonction, mais comment on fait à fin de pouvoir les composer d’une façon aussi élégante comme dans la code ??
Avant de vous répondre
Permettez moi de revenir sur le deuxième problème et de vous proposer une deuxième version :)
Encore une fois ; une seule ligne de code concis qui représente exactement l’expression du besoin.
Supposons que demain le besoin change, on veut plus les produits mais uniquement leurs références groupé par category
Pour s’adapter avec cette nouvelle demande, dans la version précédente, il faut avoir une maîtrise complète du code
il faut être très prudent pour ne pas introduire des bugs
Avec la nouvelle version , c’est toutjours dans une seule ligne , nous avons compris qu’il s’agit d’un mapping du produit sur sa réference
Il suffit d’exprimer ça.
Code de haut niveau, un code qu’on puisse lire comme une histoire, et pas comme un puzzle, un code déclarative
Pas de variables à mutter et à manipuler, Flexiblité, facile adapatation au changement,
une composition de fonctions
Tels sont les caractéristiques de la programmation fonctionnelle
Maintenant que nous avons pu souligner ensemble le style qui définit la programmation fonctionnelle, et ayant tous compris la différence par rapport
à la programmation impérative
nous allons dans cette partie de la session essayer de vous présenter les principaux mécanisme sur lesquels se base la programmation fonctionnelles
Autrement les mécanismes qui vont rendre ce style de programmation possible.
Étant donnée que le style de programmation fonctionnel donne une grande importance a l'immutabilité, les structures de contrôle classiques (tel que for, while, do … while) n’ont pas de place dans ce paradigme car implicitement la condition d'arrêt de ces boucles nécessite le test sur une variable dont l'état doit être modifié sinon on aura une boucle infini.
Face a ce dilemme on a recours a la récursivité ou plus précisément les fonctions récursives.
Une fonction récursive est tous simplement une fonction qui fait appel a elle même.
Vous allez me dire, oui on est tous conscient du concept des fonctions récursives mais on ne les utilises pas car une fonction récursive est plus lente que son équivalente itérative (on utilisant des boucles classiques). Mais ceci n’est pas un défaut inhérent à récursivité, mais un manque de support de ce concept dans le langage de programmation qu’on utilise (Java est bien sur concerne par ce manque).
Pour illustrer ceci, considérant l’exemple simple du calcul du factoriel d’un nombre entier donne.
La définition mathématique du factoriel est simplement:
le factoriel de 1 est 1
le factoriel d’un nombre n supérieur a 1 est égal a: n fois le factoriel de (n-1)
Dans Java on peut implémenté cette fonction de deux manières: itérative ou récursive.
Pour la fonction itérative factorialImperative, elle utilise la mutabilité de la variable locale r en plus d’une boucle for pour calculer le résultat.
Considérant maintenant l’autre définition récursive, elle mirror (reprend) exactement la définition mathématique du factoriel et se lis simplement: Si le nombre entré est 1 le résultat est 1 sinon on multiplie le nombre par factoriel du nombre juste avant.
On constate que le code récursive est d’un niveau d’abstraction plus élevé, il n’y a pas de changement d'état et on peut le lire comme une histoire, en contraste dans le code itératif on doit faire plus d’effort mental : suivre l'évolution de la variable local, vérifié si les conditions d'arrêt de la loupe sont correctes, en plus la solution iterative n’a aucune relation avec la définition de l’enonce initial (on doit faire plus d’effort pour arriver a la solution impérative que son contre part récursive).
Normalement on doit avoir la liberté de choisir le style de fonction (itérative ou récursive) le plus adapte au probleme en question.
Mais pourquoi dans Java (et autre langages impératifs) on a tendance a éviter les fonctions récursives, tout simplement car la solution s'avère moins performante qu’une solution itérative.
La solution récursive n’est pas optimal même dans un langage fonctionnel, tous simplement on aura une explosion du stack pour les grand nombre, car on continue a empiler les résultats intermédiaires n’effectuant la multiplication que lorsqu’on arrive au cas de base (dans ce cas le nombre 1).
Considérant par exemple le calcul du factorial de 5:
La forme de cette exécution est caractérisée par une expansion (ou le stack ne fait que grandir: appels recursives) et ensuite une contraction (on combine les resultats intermidiaires)
Pour éviter l’accumulation des résultats intermédiaires on a recours a l’utilisation d’un accumulateur, tous simplement un accumulateur est un paramètre additionnel qu’on passe a la fonction est qui contient le résultat final des appels précèdents.
On réécrit alors la définition de notre fonction récursive en utilisant l’accumulateur, la fonction utilitaire factorialHelper prends en plus du nombre n, un autre nombre acc, et dans son appel récursif a elle même elle multiplie l’ancien accumulateur par le nombre en question, decremente n et passe ces deux parametres pour le prochain appel recursif.
Voyans maintenat l’execution de cette fonction:
Dans cette exécution on vois que le stack n’augmente pas, les résultats intermédiaires sont passes dans le deuxième paramètre.
Un point fort des langage fonctionnel est qu’il peuvent détecter cette optimisation et le code générer et équivalent a une solution itérative n’augmentant pas la taille pour empiler les appels récursifs inutilement. Cette technique est appelée “call-tail optimisation”.
Malheureusement cette technique n’est pas supporter par le langage Java.
Avant Java 8, les fonctions été considérées comme des citoyens de seconds rond. Il est bien évident que l’on été conscient de l’importance que jouent les fonctions; en tous cas c’est les fonctions qui font tous le travail, les objets sert a les regrouper et réduire leur visibilité.
Meme avec Java 8, on n’a toujours pas la notion de function-literal, on est oblige de passer par des interfaces.
Pré Java 8, on peut implémenter cette logique en passant par une instance d’une classe anonyme.
Considérons deux exemples:
Dans le premier cas on crée un Thread qui va afficher “Hello World” lors de son exécution
Dans le second exemple on associe une call-back qui affichera le message “Button Pressed” lorsqu’on click sur le boutton
Dans ces deux exemple ce qu’on veux exécuter est simple (le code en vert) mais on se trouve obligé de mettre notre code dans des templates qui n’ajoute aucune valeur a notre code. (c’est ce que le professor Venkat Subramaniam appele ceremonie).
Heureusement Java8 a pris conscience qu’elle doit enfin éliminer cette façon tordu et verbeuse, meme si avec support limite (on a tjr pas la notion des function literal) en introduisant les expressions lambda aussi appele fonctions anonymes.
Proposition: Les fonctions servent à abstraire un comportement pour la réalisation d’un traitement, c’est le mécanisme le plus important dans un langage de programmation car avec on peut créer le coeur du process et sans lui on peut rien faire.
le fait que ce traitement soit parametrizable, pour qu’on puisse les passer dans les parametres , le retourner, les instancier comme une valeur, comme toute autre valeur (int, enum, object...) on était obligé de le wrapper dans un objet => verbosité
L’idiom qui consiste à factoriser un comportement commun (comme ce que venons de voir avec l’API stream) et le passer en paramètre ne pouvait pas se fait sans cette opération qui consiste à wrapper ce comportement dans un objet.
Toute entité ayant cette possiblité d’être déclaré et instancité, et capable de passer en parametre et etre retourné comme résultat, est concidéré uen entité de premiere classe
(en orienté objet et java actuellement
les fonctions sont soit des méthodes statiques, soit des méthodes membres d’objets, dans le premier cas
il s’agit des fonction utilitaires factorisant un traitement commain qu’on appelle dans notre code pour éviter la répitition
Mais impossible de les passer comme paramètre ...)
Cette solution est nomme “lambda expression” ou “fonction anonyme”.
On peut facilement adapter notre ancien style d’utilisation des classes anonymes pour utiliser les expressions lambda simplement par supprimession du code inutile, (code bare en rouge, les parentheses sont optionel si on a une seule ligne) et ajouter une fleche entre le tuple representant les arguments et le core du traitement.
Voici la version final de ces deux exemples après élimination du code inutile.
Le deux lignes font exactement la même chose que ce qu’on pouvait avant réaliser avec les classes anonymes, et comme cerise sur le gâteau le compilateur est maintenant capable d'inférer le type du paramètre e en tant que ActionEvent.
En fait on est pas obliger de renseigner le type des paramètres des fonctions anonymes.
Dans le cas ou on veut utiliser une méthode déjà existante, on peut utiliser le “Pattern Adapter”, on va créer une autre classe qui implémente l’interface attendue, et dans son implementation on va déléguer les traitements vers une instance privée de la classe ayant déjà la fonction qu’on veut réutiliser). Ou créer une classe anonyme qui fait appel a la methode, heureusement Java 8 a introduite la notion de méthodes références.
Une restriction importante des expressions lambda, c’est que l’interface a laquelle s’attend le client (Target Type), doit avoir une seule méthode abstraite. C’est ce qu’on appel interface SAM (Single Abstract Method), on peut avoir d’autre méthodes dans l'interface mais pour ces méthode on doit fournir une implementation. Oui avec Java 8 on peut implémenter des méthodes ce qu’on appel default methodes.
C’est grâce au Single Abstract Method interface que le compilateur Java 8 est capable de créer une implementation de l’interface attendue et d'inférer les type des parametres.
Une autre restriction des expressions lambda c’est qu’on ne peut les affecter qu’au variable de type SAM interface dont la seule méthode non implémenté est coherente avec la signature du lambda. On ne peut pas affecter une lambda même a une variable de type objet.
Vue l’obligation que la signature de la méthode lambda doit être cohérente avec la signature d’une interface donnée, et pour ne pas obliger les développeur a créer une interface appropriée a chaque fois qu’il veut utiliser une fonction anonyme. le package java.util.function offre plusieurs SAM interfaces adaptées a plusieurs circonstances, on trouve par exemple:
- Predicate
Consumer: pour toute lambda expression qui prend un paramètre et n retourne aucune valeure
Supplier : Pour tout lambda expression qui ne prend aucun paramètre et retourne une valeure
Function: Prend un paramètre et retourne une valeur
….
Selon votre cas d’utilisation vous avez a choisir l’interface SAM qui repond le mieux a votre besoin.
Dans le cas ou on veut utiliser une méthode déjà existante, on peut utiliser le “Pattern Adapter”, on va créer une autre classe qui implémente l’interface attendue, et dans son implementation on va déléguer les traitements vers une instance privée de la classe ayant déjà la fonction qu’on veut réutiliser). Ou créer une classe anonyme qui fait appel a la methode, heureusement Java 8 a introduite la notion de méthodes références.
Une restriction importante des expressions lambda, c’est que l’interface a laquelle s’attend le client (Target Type), doit avoir une seule méthode abstraite. C’est ce qu’on appel interface SAM (Single Abstract Method), on peut avoir d’autre méthodes dans l'interface mais pour ces méthode on doit fournir une implementation. Oui avec Java 8 on peut implémenter des méthodes ce qu’on appel default methodes.
C’est grâce au Single Abstract Method interface que le compilateur Java 8 est capable de créer une implementation de l’interface attendue et d'inférer les type des parametres.
Une autre restriction des expressions lambda c’est qu’on ne peut les affecter qu’au variable de type SAM interface dont la seule méthode non implémenté est coherente avec la signature du lambda. On ne peut pas affecter une lambda même a une variable de type objet.
Vue l’obligation que la signature de la méthode lambda doit être cohérente avec la signature d’une interface donnée, et pour ne pas obliger les développeur a créer une interface appropriée a chaque fois qu’il veut utiliser une fonction anonyme. le package java.util.function offre plusieurs SAM interfaces adaptées a plusieurs circonstances, on trouve par exemple:
- Predicate
Consumer: pour toute lambda expression qui prend un paramètre et n retourne aucune valeure
Supplier : Pour tout lambda expression qui ne prend aucun paramètre et retourne une valeure
Function: Prend un paramètre et retourne une valeur
….
Selon votre cas d’utilisation vous avez a choisir l’interface SAM qui repond le mieux a votre besoin.
Dans le cas ou on veut utiliser une méthode déjà existante, on peut utiliser le “Pattern Adapter”, on va créer une autre classe qui implémente l’interface attendue, et dans son implementation on va déléguer les traitements vers une instance privée de la classe ayant déjà la fonction qu’on veut réutiliser). Ou créer une classe anonyme qui fait appel a la methode, heureusement Java 8 a introduite la notion de méthodes références.
Une restriction importante des expressions lambda, c’est que l’interface a laquelle s’attend le client (Target Type), doit avoir une seule méthode abstraite. C’est ce qu’on appel interface SAM (Single Abstract Method), on peut avoir d’autre méthodes dans l'interface mais pour ces méthode on doit fournir une implementation. Oui avec Java 8 on peut implémenter des méthodes ce qu’on appel default methodes.
C’est grâce au Single Abstract Method interface que le compilateur Java 8 est capable de créer une implementation de l’interface attendue et d'inférer les type des parametres.
Une autre restriction des expressions lambda c’est qu’on ne peut les affecter qu’au variable de type SAM interface dont la seule méthode non implémenté est coherente avec la signature du lambda. On ne peut pas affecter une lambda même a une variable de type objet.
Vue l’obligation que la signature de la méthode lambda doit être cohérente avec la signature d’une interface donnée, et pour ne pas obliger les développeur a créer une interface appropriée a chaque fois qu’il veut utiliser une fonction anonyme. le package java.util.function offre plusieurs SAM interfaces adaptées a plusieurs circonstances, on trouve par exemple:
- Predicate
Consumer: pour toute lambda expression qui prend un paramètre et n retourne aucune valeure
Supplier : Pour tout lambda expression qui ne prend aucun paramètre et retourne une valeure
Function: Prend un paramètre et retourne une valeur
….
Selon votre cas d’utilisation vous avez a choisir l’interface SAM qui repond le mieux a votre besoin.
On arrive maintenant au mécanisme d'abstraction (et de composition) le plus utiliser dans les langages fonctionnels, et ce des fonctions de haut niveau (HOF).
Une fonction de haut niveau est simplement une fonction qui prend des fonction en tant que paramètre ou retourne une fonction en tant que resultat.
Le concept des HOF nous permet d'implémenter facilement le pattern Strategy (ou ce qu’est aussi appelé parametrisation du comportement).
Considérant les trois exemples suivants:
On a une fonction pour le calcul de la somme des entiers entre un interval a et b
La deuxieme fonction consiste elle a calculer la somme des cube des entier entre un interval a et b
La troisieme fonction calcul la somme des factoriels des entiers entre a et b
Les trois fonctions sont récursives et on n’a pas utiliser le cumulateur pour TCO, pour simplifier les exemples.
On voit que la forme des trois fonctions est semblable, elle diffèrent uniquement au niveau du code colore en rouge. Pour unifier ces trois fonctions on a recours au Design Pattern Strategy dans la terminologie des langages OO, mais dans les langages fonctionel on dit tous simplement qu’on va utiliser une fonction de haut niveau.
Ici on vois qu’on a définit une fonction récursive higherSum de la même form que les trois précédentes fonctions, la seule différence ici c’est que higherSum prend en plus une fonction f comme argument, cette fonction lui permettra d'implémenter la logique de somme on appliquant la fonction f sur chaque élément de l’interval a et b.
Bien sur étant donne que l’interface Function<Integer, Integer> est une interface SAM du package java.util.function (dont les type input et output sont pris ici pour Integer), on peut invoquer la fonction higherSum on lui passant une expression lambda comme paramètre.
Les expressions lambda sont colorées en vert.
On peut ainsi utiliser cette fonction pour calculer facilement:
La somme des carres des entiers entre a et b en lui passant la fonction anonyme: x -> x*x
La somme des cube des entiers entre a et b en lui passant la fonction anonyme: x -> x*x*x
ou la somme de … les possibilités sont illimités
On peut même utilise un concept du paradigme fonctionnel celui de currying et dont le principal objectif est de voir une fonction a n parametres retournant un résultat de type R, comme une HOF qui prend un seul paramètre et retourne un fonction qui prend (n-1) paramètres et retourne un résultat de type R.
Dans cet exemple la fonction curriendSum prend en parametre la fonction qu’on veut applliquer apres sur chaque element de l’interval, et retourn une fonction de type java.util.function.BiFunction qui prend deux parametre de type Integer et retourne un resultat de type Integer.
On peut ensuite utilise cette fonction pour le calcul des sommes sur un interval comme precedemment.
Considérons, l'exemple suivant:
On a un dépôt de produits, qui nous retourne une collections de produits, et on veut par exemple récupérer la référence de 10 produits répondant a un critère donnée.
Avant Java 8, on est oblige de parcourir la collection des produits, et pour chaque élément rencontre vérifier si il répond au critère voulu, si oui on l’ajoute a une liste produtsOfInterest, incrémenter une variable contenant numberOfProductSoFar qui si elle dépasse 10 on sort de la boucle.
Cette façon de faire est appelée “external iteration”, et c’est le développeur qui est en charge du parcourt de la collection et si demain il veut paralléliser ce traitement il doit tous repenser.
Avec Les Streams API l’iteration est interne et elle invisible par le developpeur, tous ce qu’il a a faire c’est de preciser les operations voulues (filter, map, limit, ….) c’est operations sont appelees “operations intermidiaires”, et chaque operation intermidiaire genere un autre Stream sans pour autant executant son traitement ( le stream final resultant regroupe l’ensemble des operations intermidiaires) de cette facon la source (qui peut etre soit une Collection, tableau, lignes d’un fichier ou autre) n’est parcouru qu’une seule fois si besoin est.
Un autre type d’operations sur les Streams est appele “operation terminal”. C’est qu’on on appele ces operations que la source est enfin parcourus (en interne) et pour chaque element on applique les operations intermidiaires.
Un autre avantage de l’iteration interne c’est qu’on peut paralleliser facilement ce traitement on appelant parallelStream au lieu de stream, ou appeler la method parallel() sur un stream ou rendre un stream sequentiel en applement la methode sequential() sur le stream.
1. A stream of infinite ints (tous les nombres entiers naturels)
2. Un stream nepeut etre consomer qu’une seul fois !!!