Syntaxe des Lambdas et Streams en Java 8
Syntaxe des Lambdas et Streams en Java 8
Dans notre exemple précédent, notre interface Soin est une interface fonctionnelle car elle n 'a
qu'une méthode à redéfinir (Single Abstract Method, ou interface SAM). Comme je vous le disais,
ce type d'interface sera le pillier de l'utilisation des lambdas donc, pour s'assurer que le contrat
est bien respecté, Java propose d'annoter l'interface avec @FunctionalInterface , comme
ceci :
package com.sdz.comportement;
@FunctionalInterface
Le package java.util.function
Parmi toutes les interfaces disponibles, voici cinq des interfaces utilisant des génériques et
représentant un type de traitement :
Exemple :
Avec les streams nous allons pouvoir parcourir, filtrer, manipuler, transformer nos données de
façon séquentiel ou parallèles.
Un stream ne stocke aucune donnée, il se contente de les transférer vers une suite
instruction a opérer ;
Un stream ne modifie pas les données qu'il reçoit de sa source (flux, collection, tableau,
…). S'il doit modifier des données, il doit au construire un nouveau stream.
Un stream est a usage unique : une fois utilisé complètement, impossible de l'utiliser une
seconde fois. Si nous devons réutiliser les données d'une source une seconde fois, nous
devrons recréer un second stream.
Un stream peut être infini si on ne clos pas le traitement dessus.
Les traitements fait sur un stream peuvent être de deux natures :
o Intermédiaire : ce genre d'opération conserve le stream ouvert ce qui permet
d'effectuer d'autre opérations dessus. Nous pourrons voir ceci lors de l'utilisation
des méthodes map() ou filter() .
o Terminale : c'est l'opération finale du stream, c'est ce qui lance la «
consommation » du stream. La méthode reduce() en est un exemple.
Stream<Personne> sp = listP.stream();
sp.forEach(System.out::println);
Filtrage
Celle-ci se fait grâce à la méthode filter() qui accepte un Predicate< ? Super T> en
paramètre. En conservant notre exemple précédent, si nous prenions maintenant uniquement les
personne de plus de 50 Kg.
Stream<Personne> sp = listP.stream();
sp.forEach(System.out::println);
System.out.println("\nAprès le filtre");
sp = listP.stream();
sp. filter(x -> x.getPoids() > 50)
.forEach(System.out::println);
L'opération map
L'opération map
Ici, nous allons appliquer une opération sur chaque élément afin de ne récupérer que ce qui nous
intéresse. Par exemple, en ne conservant que le poids de personnes que nous avons filtré.
Stream<Personne> sp = listP.stream();
sp.forEach(System.out::println);
sp = listP.stream();
.forEach(System.out::println);
Ce qui donne le résultat suivant :
Après le filtre
70.0
65.0
65.0
120.0
90.0
Reduce
Cette opération a pour but d'agréger le contenu de votre stream pour fournir un résultat unique.
Que diriez-vous d'avoir la somme des poids des personnes que nous avons filtré précédemment.
Stream<Personne> sp = listP.stream();
sp.forEach(System.out::println);
System.out.println(sum);
Ce qui nous donne :
sp = listP.stream();
System.out.println(sum.get());
Ceci nous retourne une belle exception :
Exception in thread "main" java.util.NoSuchElementException: No value
present
at java.base/java.util.Optional.get(Unknown Source)
at TestStream.main(TestStream.java:27)
C'est normal puisqu'il n'y a personne qui pèse plus de 250 Kg, impossible de faire la somme.
Dans ce cas, nous sommes dans l'obligation de gérer la possibilité d'absence de résultat, ce qui
est une bonne chose car trop souvent omise par les développeurs. Voici comment s'y prendre :
sp = listP.stream();
if(sum.isPresent())
System.out.println(sum.get());
else
La méthode isPresent() permet de savoir si l'objet Optional contient une valeur ou non,
donc si notre traitement a fonctionné. Il est également possible de contrôler le résultat en utilisant
une valeur par défaut en cas de problème. Regardez plutôt :
sp = listP.stream();
System.out.println(sum.orElse(0.0));
Count
Vous l'aurez compris, celle-ci compte le nombre d'éléments restant après les opérations
précédentes.
sp = listP.stream();
.count();
Voici un code qui va nous donner une liste contenant tous les poids qui satisfont les opérations
précédentes réalisées :
sp = listP.stream();
.collect(Collectors.toList());
System.out.println(ld);
Opération Rôle
Stream<T> filter(Predicate<? super T> predicate)
filter Stateless Filtrer tous les éléments pour n'inclure dans le Stream de sortie que les
éléments qui satisfont le Predicat
<R> Stream<R> map(Function<? super T,? extends R> mapper)
map Stateless Renvoyer un Stream qui contient le résultat de la transformation de
chaque élément de type T en un élément de type R
xxxStream mapToxxx(ToxxxFunction<? super T> mapper)
mapToxxx(Int,
Stateless
Long or Double) Renvoyer un Stream qui contient le résultat de la transformation de
chaque élément de type T en un type primitif xxx
<R> Stream<R> flatMap(Function<T,Stream<? extends R>> mapper)
Renvoyer un Stream avec l'ensemble des éléments contenus dans les
flatMap Stateless Stream<R> retournés par l'application de la Function sur les éléments
de type T. Ainsi chaque élément de type T peut renvoyer zéro, un ou
plusieurs éléments de type R.
xxxStream flatMapToxxx(Function<? super T,? extends xxxStream>
mapper)
flatMapToxxx
(Int, Long or Renvoyer un Stream avec l'ensemble des éléments contenus dans les
Double) xxxStream retournés par l'application de la Function sur les éléments
de type T. Ainsi chaque élément de type T peut renvoyer zéro, un ou
plusieurs éléments de type primitif xxx.
Stream<T> distinct()
distinct Stateful
Renvoyer un Stream<T> dont les doublons ont été retirés. La
détection des doublons se fait en invoquant la méthode equals()
sorted Stateful Stream<T> sorted()
Renvoyer un Stream dont les éléments sont triés dans un certain ordre.
La surcharge sans paramètre tri dans l'ordre naturel : le type T doit
donc implémenter l'interface Comparable car c'est sa méthode
compareTo() qui est utilisée pour la comparaison des éléments deux à
deux
La surcharge avec un Comparator l'utilise pour déterminer l'ordre de
tri.
Stream<T> peek(Consumer <? super T>)
peek Stateless
Renvoyer les éléments du Stream et leur appliquer le Consumer fourni
en paramètre
Stateful Stream<T> limit(long)
limit
Short- Renvoyer un Stream qui contient au plus le nombre d'éléments fournis
Circuiting en paramètre
Stream<T> skip(long)
skip Stateful
Renvoyer un Stream dont les n premiers éléments ont été ignorés, n
correspondant à la valeur fournie en paramètre
Stream<T> sequential()
sequential
Renvoyer un Stream équivalent dont le mode d'exécution des
opérations est séquentiel
Stream<T> parallel()
parallel
Renvoyer un Stream équivalent dont le mode d'exécution des
opérations est en parallèle
Stream<T> parallel()
unordered
Renvoyer un Stream équivalent dont l'ordre des éléments n'a pas
d'importance
Stream<T> onClose(Runnable)
Méthode Rôle
Renvoyer un Stream qui contient le résultat de
<R> Stream<R> map(Function<? l'application de la fonction sur chaque élément
super T,? extends R> mapper) du Stream. T est le type des éléments du Stream
et R est le type des éléments résultat
DoubleStream Renvoyer un DoubleStream contenant le résultat
mapToDouble(ToDoubleFunction<? de l'application de la fonction passé en
super T> mapper) paramètre à tous les éléments du Stream
Renvoyer un IntStream contenant le résultat de
IntStream mapToInt(ToIntFunction<?
l'application de la fonction passé en paramètre à
super T> mapper)
tous les éléments du Stream
LongStream Renvoyer un LongStream contenant le résultat de
mapToLong(ToLongFunction<? super l'application de la fonction passé en paramètre à
T> mapper) tous les éléments du Stream
Résultat :
1.[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
Résultat :
1.[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
La différence entre une collection et un
Stream
Bien que les Collections et les Streams semblent avoir des similitudes, ils ont des
objectifs différents :
Un Stream n'est pas une structure qui stocke des données. Elle permet de traiter
les différents éléments d'une source en appliquant différentes opérations
Un Stream permet de mettre en oeuvre la programmation fonctionnelle : le
résultat d'une opération ne doit pas modifier l'élément sur lequel elle opère ni
aucun autre élément de la source. Par exemple, une opération de filtre ne doit pas
retirer des éléments de la source mais créer un nouveau Stream qui contient
uniquement les éléments filtrés
De nombreuses opérations d'un Stream attendent en paramètre une expression
lambda
Il n'est pas possible d'accéder à un élément grâce à un index : seul le premier
peut être obtenu ou il faut exporter le résultat sous la forme d'une collection ou
d'un tableau
Il est possible de paralléliser l'exécution des opérations. Cette exécution parallèle
utilise alors le framework Fork/Join
Un Stream n'a pas forcement de taille fixe : le nombre d'éléments à traiter peut
potentiellement être infini. Ils peuvent consommer des données jusqu'à ce qu'une
condition soit satisfaite : des méthodes comme limit() ou findFirst() peuvent alors
permettre de définir une condition d'arrêt
Une opération ne peut consommer qu'une seule fois un élément. Un nouveau
Stream doit être recréer pour traiter de nouveau un élément comme le fait un
Iterator
Les Streams ne stockent pas de données. Ils transportent et utilisent les données
issues d'une source qui les stockent ou les génèrent
Les Streams exécutent un pipeline d'opérations sur les données de leur source
Les Streams sont de nature fonctionnel : les opérations d'un Stream produisent
des résultats mais ne devrait pas modifier les données de leur source
Les opérations intermédiaires des Streams sont exécutées de manière lazy. Ce
mode de fonctionnement permet d'effectuer des optimisations lors de l'exécution
en un seul passage du pipeline d'opérations
- import java.time.LocalDate;
- import java.time.LocalDateTime;
- import java.time.LocalTime;
- import java.time.Month;
-
- public class Test {
- public static void main(String[] args) {
- // Get the current date and time
- LocalDateTime currentTime = LocalDateTime.now();
- System.out.println("Date et heure courante : " + currentTime);
-
- LocalDate date1 = currentTime.toLocalDate();
- System.out.println("Date courante : " + date1);
-
- Month mois = currentTime.getMonth();
- int jour = currentTime.getDayOfMonth();
- int heure = currentTime.getHour();
-
- System.out.println("Mois : " + mois +", jour : " + jour +", heure : " + heure);
-
- //Avoir le 25 décembre 2023
- LocalDateTime date2 =
currentTime.withDayOfMonth(25).withYear(2023).withMonth(12);
- System.out.println("Date modifiée : " + date2);
-
- //une autre façon
- LocalDate date3 = LocalDate.of(2023, Month.DECEMBER, 25);
- System.out.println("Autre façon de faire : " + date3);
-
- //On peut même parser une date depuis un String
- LocalTime parsed = LocalTime.parse("20:15:30");
- System.out.println("Date parsée : " + parsed);
- }
- }
-
- Ce qui nous donne le résultat suivant :
- Date et heure courante : 2018-01-19T09:29:44.150862600
- Date courante : 2018-01-19
- Mois : JANUARY, jour : 19, heure : 9
- Date modifiée : 2023-12-25T09:29:44.150862600
- Autre façon de faire : 2023-12-25
- Date parsée : 20:15:30
Il est également possible de manipuler ces objets afin d'ajouter ou de retirer du temps, que ce
soit en jour, mois, année, heure... Ceci peut se faire grâce aux méthodes présente dans les objet
LocalDate et LocalDateTime ainsi que la classe ChronoUnit.
import java.time.LocalDateTime;
import java.time.Month;
import java.time.temporal.ChronoUnit;
Duration et Period
Ces deux objets permettent de gérer les intervales de temps. Period va gérer des intervales de
dates (2 ans, 1 mois, 15 jours...) alors de Duration va gérer des intervales de temps machine,
donc en seconde. Ces classes possèdent également de quoi faire la différence entre deux dates,
voyons tout de suite ceci en action :
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.Period;
import java.time.temporal.ChronoUnit;
}
Ce qui nous donne :
Période : 3
Durée : 6180
Vous avez pu remarquer que nous avons utilisé de nouveau l'objet ChronoUnit afin de savoir
de combien de jour notre période est faite. Nous avons fait comme ceci car l'objet période ne
calcul pas automatiquement le nombre de jour d'un écart demandé en année.
TemporalAdjusters
Cette classe va sûrement vous sauver la vie à maintes reprise car elle permet d'ajuster une date
en fonction d'éléments relatif comme avoir le prochain mardi, le premier samedi du mois, etc. Vu
sa nature, vous aurez compris que cet objet manipule des objets LocalDate et non des objets
LocalDateTime .
Voici un code d'exemple depuis notre date fétiche.
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
System.out.println(prochainSamedi);
LocalDate troisiemeMardi =
String.valueOf(ldtLocalBis.getDayOfWeek()).equals("TUESDAY") ?
ldt2
.with(TemporalAdjusters.next(DayOfWeek.TUESDAY))
.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)) :
ldt2.with(TemporalAdjusters.next(DayOfWeek.TUESDAY))
.with(TemporalAdjusters.next(DayOfWeek.TUESDAY))
.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
System.out.println(troisiemeMardi);
}
Ce qui nous donne :
2018-12-29
2019-01-15
Jusque là, tout va bien. Regardons maintenant comment nous pouvons gérer les fuseaux
horaires.
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Map;
System.out.println("");
Nous récupérons toutes les valeurs de la Map contenant les fuseaux et nous l'affichons ainsi
que la règle à appliquer sur les heures. Ceci peut être fait en récupérant une instance de
ZoneId grâce à sa méthode of() . Ceci permet de lister tous les fuseaux utilisables, que voici :
-10:00 -- ZoneRules[currentStandardOffset=-10:00]
Australia/Darwin -- ZoneRules[currentStandardOffset=+09:30]
Pacific/Guadalcanal -- ZoneRules[currentStandardOffset=+11:00]
America/Indiana/Indianapolis -- ZoneRules[currentStandardOffset=-05:00]
Africa/Cairo -- ZoneRules[currentStandardOffset=+02:00]
Asia/Tokyo -- ZoneRules[currentStandardOffset=+09:00]
America/Phoenix -- ZoneRules[currentStandardOffset=-07:00]
Pacific/Auckland -- ZoneRules[currentStandardOffset=+12:00]
Asia/Shanghai -- ZoneRules[currentStandardOffset=+08:00]
Australia/Sydney -- ZoneRules[currentStandardOffset=+10:00]
Africa/Addis_Ababa -- ZoneRules[currentStandardOffset=+03:00]
America/Puerto_Rico -- ZoneRules[currentStandardOffset=-04:00]
Asia/Dhaka -- ZoneRules[currentStandardOffset=+06:00]
America/Chicago -- ZoneRules[currentStandardOffset=-06:00]
Asia/Yerevan -- ZoneRules[currentStandardOffset=+04:00]
Africa/Harare -- ZoneRules[currentStandardOffset=+02:00]
Europe/Paris -- ZoneRules[currentStandardOffset=+01:00]
America/St_Johns -- ZoneRules[currentStandardOffset=-03:30]
America/Anchorage -- ZoneRules[currentStandardOffset=-09:00]
-05:00 -- ZoneRules[currentStandardOffset=-05:00]
Asia/Ho_Chi_Minh -- ZoneRules[currentStandardOffset=+07:00]
Pacific/Apia -- ZoneRules[currentStandardOffset=+13:00]
America/Sao_Paulo -- ZoneRules[currentStandardOffset=-03:00]
Asia/Karachi -- ZoneRules[currentStandardOffset=+05:00]
-07:00 -- ZoneRules[currentStandardOffset=-07:00]
America/Los_Angeles -- ZoneRules[currentStandardOffset=-08:00]
Asia/Kolkata -- ZoneRules[currentStandardOffset=+05:30]
America/Argentina/Buenos_Aires -- ZoneRules[currentStandardOffset=-03:00]
Voici un code qui parse une date/heure et qui nous donne sa valeur en fonction d'un fuseau
horaire :
ZoneId.of("Europe/Paris"),
ZoneId.of("Asia/Tokyo"),
ZoneId.of("America/Anchorage")
);
lzi .stream()
.forEach((x) -> {
});
Résumer Java 8
Java 8 Interview Questions
1. Pourquoi avons-nous encore besoin de passer à Java?
2. Java SE 8 New Features?
3. Avantages de Java SE 8 Nouvelles fonctionnalités?
4. Qu'est-ce que l'expression Lambda?
5. Quelles sont les trois parties d'une expression Lambda? Quel est le type d'expression
Lambda?
6. Qu'est-ce qu'une interface fonctionnelle? Qu'est-ce que SAM Interface?
7. Est-il possible de définir notre propre interface fonctionnelle? Qu'est-ce que
@FunctionalInterface? Quelles sont les règles pour définir une interface fonctionnelle?
8. Les annotations @FunctionalInterface sont-elles obligatoires pour définir une interface
fonctionnelle? Quelle est l'utilisation de l'annotation @FunctionalInterface? Pourquoi avons-
nous besoin d'interfaces fonctionnelles en Java?
9. Quand choisissons-nous l' API Java 8 Stream? Pourquoi devons-nous utiliser l'API Java 8
Stream dans nos projets?
10. Expliquez les différences entre l'API de collection et l'API de flux?
11. Qu'est-ce que Spliterator dans Java SE 8? Différences entre Iterator et Spliterator dans
Java SE 8?
12. Qu'est-ce qui est facultatif dans Java 8? Quelle est l'utilisation de Optional? Avantages de
Java 8 Optional?
13. Qu'est-ce que l'inférence de type? Est-ce que Type Inference est disponible dans les
anciennes versions comme Java 7 et Before 7 ou est-il disponible uniquement dans Java
SE 8?
L'expression Lambda est un bloc de code sans nom, avec ou sans paramètres et avec ou sans
résultats. Ce bloc de code est exécuté à la demande.
Liste de paramètres
Une expression Lambda peut contenir zéro ou un ou plusieurs paramètres. C'est optionnel.
“->” est connu sous le nom d’opérateur Lambda Arrow. Il sépare la liste des paramètres et
le corps.
Le type de «Journal Dev» est java.lang.String. Le type de «vrai» est booléen. De la même
manière, quel est le type d'une expression Lambda?
Le type d'une expression lambda est une interface fonctionnelle .
SAM Interface signifie Single Abstract Method Interface. L'API Java SE 8 a défini de nombreuses
interfaces fonctionnelles.
Lorsque nous voulons effectuer la base de données comme des opérations. Par exemple,
nous voulons effectuer une opération groupby, une opération orderby, etc.
Quand voulez-vous effectuer des opérations rapidement?
Quand on veut écrire une programmation de style fonctionnel.
Quand on veut effectuer des opérations parallèles.
Quand voulez utiliser l'itération interne
Quand on veut effectuer des opérations de pipeline.
Quand on veut atteindre de meilleures performances.
Qu'est-ce qui est facultatif dans Java 8? Quelle est
l'utilisation de Optional? Avantages de Java 8 Optional?
Facultatif:
Facultatif est une classe finale introduite dans Java SE 8. Elle est définie dans le package
java.util.
Il est utilisé pour représenter des valeurs optionnelles existantes ou non existantes. Il peut
contenir une valeur ou une valeur zéro. S'il contient une valeur, nous pouvons l'obtenir. Sinon,
nous ne recevons rien.
C'est une collection bornée qui contient au plus un élément. C'est une alternative à la valeur
«null».
Avant Java 7: -
Explorons les tableaux Java. Définissez une chaîne de tableau avec les valeurs indiquées ci-
dessous:
Java 7: -
Oracle Corporation a introduit la nouvelle fonctionnalité «Diamond Operator» dans Java SE 7 afin
d’éviter toute définition de type inutile dans Generics.
Java SE 8: -
Oracle Corporation a beaucoup amélioré ce concept d'inférence de type dans Java SE 8. Nous
utilisons ce concept pour définir les expressions lambda, les fonctions, les références de
méthode, etc.
Dans Internal Iteration, comme l’API Java s’occupe de la itération d’éléments en interne,
nous n’avons pas le contrôle sur l’itération.
Dans External Itération, comme l’API Java ne s’occupe PAS d’itérer des éléments, nous
avons beaucoup de contrôle sur Itération.
Lorsque nous avons besoin de plus de contrôle sur l'itération, nous pouvons utiliser
l'itération externe.
Lorsque nous n’avons PAS besoin de plus de contrôle sur les itérations, nous pouvons
utiliser les itérations internes.
Lorsque nous devons développer des applications hautement simultanées et parallèles et
nous, nous devons utiliser l'itération interne.
Dans Java SE 8, nous pouvons fournir des implémentations de méthodes dans des interfaces en
utilisant les deux nouveaux concepts suivants:
Nous avons besoin de méthodes par défaut pour les raisons suivantes:
Nous pouvons conserver les méthodes Helper ou Utility spécifiques à une interface dans la
même interface plutôt que dans une classe Utility distincte.
Nous n'avons pas besoin de classes d'utilitaires distinctes telles que Collections, Tableaux,
etc. pour conserver les méthodes d'utilitaires.
Séparation claire des responsabilités. C’est-à-dire que nous n’avons pas besoin d’une
classe d’utilitaire pour conserver toutes les méthodes d’utilité de l’API de collection telles
que Collections, etc.
Facile d'étendre l'API.
Facile à maintenir l'API.
L'ancienne API de date ancienne de Java présente les problèmes ou inconvénients suivants
comparés à l'API de date et heure et à l'API Joda Time de Java 8.
L'API de date et heure de Java 8 résout tous les problèmes de l'ancienne API de date Java en
respectant les principes d'immuabilité et de sécurité des threads.
Cependant, Java 8 prend en charge la «mise en œuvre de méthodes dans des interfaces» en
introduisant de nouvelles fonctionnalités: méthodes par défaut dans Interface. En raison de cette
fonctionnalité, Java 8 prend en charge l'héritage multiple avec certaines limitations.