0% ont trouvé ce document utile (0 vote)
36 vues28 pages

Syntaxe des Lambdas et Streams en Java 8

Le document présente les expressions lambda en Java 8, leur syntaxe et les règles concernant les paramètres. Il aborde également les interfaces fonctionnelles, le package java.util.function, et les références de méthodes. Enfin, il explique les streams, leurs opérations de filtrage, de transformation, et d'agrégation, ainsi que des exemples pratiques d'utilisation.

Transféré par

mohamedhedibenkhoudja
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
36 vues28 pages

Syntaxe des Lambdas et Streams en Java 8

Le document présente les expressions lambda en Java 8, leur syntaxe et les règles concernant les paramètres. Il aborde également les interfaces fonctionnelles, le package java.util.function, et les références de méthodes. Enfin, il explique les streams, leurs opérations de filtrage, de transformation, et d'agrégation, ainsi que des exemples pratiques d'utilisation.

Transféré par

mohamedhedibenkhoudja
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd

JAVA 8

La syntaxe d'une expression lambda


La syntaxe d'une expression lambda est composée de trois parties :

 un ensemble de paramètres, d'aucun à plusieurs


 l'opérateur ->
 le corps de la fonction

Elle peut prendre deux formes principales :

(paramètres) -> expression;

(paramètres) -> { traitements; }

Les paramètres de l'expression doivent respecter certaines règles :

 une expression peut n'avoir aucun, un seul ou plusieurs paramètres


 le type des paramètres peuvent être explicitement déclaré ou être inféré par le
compilateur selon le contexte dans lequel l'expression est utilisée
 les paramètres sont entourés de parenthèses, chacun étant séparé par une virgule
 des parenthèses vides indiquent qu'il n'y a pas de paramètre
 s'il n'y a qu'un seul paramètre dont le type n'est pas explicitement précisé, alors
l'utilisation des parenthèses n'est pas obligatoire

Consumer<String> afficher = (param) ->


System.out.println(param)
ou
Consumer<String> afficher = param ->
System.out.println(param);

Les interfaces fonctionnelles


Ce concept, introduit depuis Java 8, permet de définir une interface n'ayant qu'une et une seule
méthode abstraite : c'est grâce à cette restriction qu'il sera possible d'utiliser les lambdas car, lors
de l'exécution, Java pourra automatiquement déterminer quelle est la signature de la méthode
que la lambda remplace et tout sera automatique.

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

public interface Soin {

public void soigne();

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 :

 java.util.function.Function<T,R> : sa méthode fonctionnelle a la signature R


apply(T t) . Elle permet donc de traité un paramètre T et de renvoyer un type R.
 java.util.function.Predicate<T> : sa méthode boolean test(T t) permet,
comme vous vous en doutez, de faire un test sur le paramètre et de retourner un
boolean en fonction du résultat.
 java.util.function.Consumer<T> : Cette interface fonctionnelle est un peu
particulière car c'est la seule qui a pour vocation de modifier les données qu'elle reçoit.
Sa méthode fonctionnelle void accept(T t) est faite pour appliquer des traitements
au paramètre passer et ne retourne rien.
 java.util.function.Supplier<T> : Celle-ci permet de renvoyer un élément de type
T sans prendre de paramètre via la méthode fonctionnelle T get().
 java.util.function.BinaryOperation : S'utilise pour les opération de type reduce
comme additionner deux int par exemple (on y reviendra lorsque nous parlerons des
streams). Sa méthode T apply(T t, T t2) prend deux T en paramètre et renvoi un
T (T BynaryOperation(T,T).

Les références de méthodes


Pour simplifier au maximum, une référence de méthode est une lambda ultra simplifiée en
utilisant ni plus ni moins qu'une méthode déjà existante dans une interface (méthode statique)
classe (méthode statique ou constructeur) ou encore une méthode d'une instance de classe.
Voici comment la syntaxe est faite :
« classe, interface ou instance » :: « Nom de la méthode »

Exemple :

//de l'instance out de la classe 'System'

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

Consumer<String> stringPrinterRef = System.out::println;


Manipulez vos données avec les streams

nouveau pattern de manipulation de données (remplaçant du pattern Iterator entre autre),


qu'elles proviennent d'un tableau, d'une collection, d'un flux de fichier (avec java.nio par
exemple)

Avec les streams nous allons pouvoir parcourir, filtrer, manipuler, transformer nos données de
façon séquentiel ou parallèles.

Les streams se trouvent dans le package java.util.stream

 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.

Parcourir une stream

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

System.out.println("\nAprès le filtre et le map");

sp = listP.stream();

sp. filter(x -> x.getPoids() > 50)

.map(x -> x.getPoids())

.forEach(System.out::println);
Ce qui donne le résultat suivant :

Je m'appelle A Nicolas, je pèse 70.0 Kg, et je mesure 1.8 cm.


Je m'appelle B Nicole, je pèse 50.0 Kg, et je mesure 1.56 cm.
Je m'appelle C Germain, je pèse 65.0 Kg, et je mesure 1.75 cm.
Je m'appelle D Michel, je pèse 50.0 Kg, et je mesure 1.68 cm.
Je m'appelle E Cyrille, je pèse 65.0 Kg, et je mesure 1.96 cm.
Je m'appelle F Denis, je pèse 120.0 Kg, et je mesure 2.1 cm.
Je m'appelle G Olivier, je pèse 90.0 Kg, et je mesure 1.9 cm.

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("\nAprès le filtre et le map et reduce");


sp = listP.stream();

Double sum = sp .filter(x -> x.getPoids() > 50)

.map(x -> x.getPoids())

.reduce(0.0d, (x,y) -> x+y);

System.out.println(sum);
Ce qui nous donne :

Je m'appelle A Nicolas, je pèse 70.0 Kg, et je mesure 1.8 cm.


Je m'appelle B Nicole, je pèse 50.0 Kg, et je mesure 1.56 cm.
Je m'appelle C Germain, je pèse 65.0 Kg, et je mesure 1.75 cm.
Je m'appelle D Michel, je pèse 50.0 Kg, et je mesure 1.68 cm.
Je m'appelle E Cyrille, je pèse 65.0 Kg, et je mesure 1.96 cm.
Je m'appelle F Denis, je pèse 120.0 Kg, et je mesure 2.1 cm.
Je m'appelle G Olivier, je pèse 90.0 Kg, et je mesure 1.9 cm.

Après le filtre et le map


410.0

System.out.println("\nAprès le filtre et le map et reduce");

sp = listP.stream();

Optional<Double> sum = sp .filter(x -> x.getPoids() > 250)

.map(x -> x.getPoids())

.reduce((x,y) -> x+y);

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 :

System.out.println("\nAprès le filtre et le map et reduce");

sp = listP.stream();

Optional<Double> sum = sp .filter(x -> x.getPoids() > 250)


.map(x -> x.getPoids())

.reduce((x,y) -> x+y);

if(sum.isPresent())

System.out.println(sum.get());

else

System.out.println("Aucun aggrégat de poids...");

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

Optional<Double> sum = sp .filter(x -> x.getPoids() > 250)

.map(x -> x.getPoids())

.reduce((x,y) -> x+y);

//Permet de gérer le cas d'erreur en renvoyant 0.0 si isPresent() == false

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

long count = sp .filter(x -> x.getPoids() > 50)

.map(x -> x.getPoids())

.count();

System.out.println("Nombre d'éléments : " + count);


Collect
Permet de récupérer le résultat des opérations successives sous une certaines forme. Cette
forme est définie par un objet Collectors (implémentant l'interface Collector ). C'est avec
cet objet que nous pourrons dire que nous souhaitons avoir notre résultat sous forme de Set ,
de Map , de List et plus encore.

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

List<Double> ld = sp.filter(x -> x.getPoids() > 50)

.map(x -> x.getPoids())

.collect(Collectors.toList());

System.out.println(ld);

 ilter(Predicate) : renvoie un Stream qui contient les éléments pour


lesquels l'évaluation du Predicate passé en paramètre vaut true
 distinct() : renvoie un Stream qui ne contient que les éléments
uniques (elle retire les doublons). La comparaison se fait grâce à
l'implémentation de la méthode equals()
 limit(n) : renvoie un Stream que ne contient comme éléments que le
nombre fourni en paramètre
 skip(n) : renvoie un Stream dont les n premiers éléments sont
ignorés

Pour rechercher une correspondance avec des éléments, un Stream


propose plusieurs opérations :

 anyMatch(Predicate) : renvoie un booléen qui précise si l'évaluation


du Predicate sur au moins un élément vaut true
 allMatch(Predicate) : renvoie un booléen qui précise si l'évaluation
du Predicate sur tous les éléments vaut true
 noneMatch(Predicate) : renvoie un booléen qui précise si l'évaluation
du Predicate sur tous les éléments vaut false
 findAny() : renvoie un objet de type Optional qui encapsule un
élément du Stream s'il existe
 findFirst() : renvoie un objet de type Optional qui encapsule le
premier élément du Stream s'il existe

Pour transformer des données, un Stream propose plusieurs opérations :

 map(Function) : applique la Function fournie en paramètre pour


transformer l'élément en créant un nouveau
 flatMap(Function) : applique la Function fournie en paramètre pour
transformer l'élément en créant zéro, un ou plusieurs éléments

Pour réduire les données et produire un résultat, un Stream propose


plusieurs opérations :

 reduce() : applique une Function pour combiner les éléments afin de


produire le résultat
 collect() : permet de transformer un Stream qui contiendra le
résultat des traitements de réduction dans un conteneur mutable

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

Stream<T> sorted(Comparator<? super T>)

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)

Renvoyer un Stream équivalent dont le handler fourni en paramètre


onClose
sera exécuté à l'invocation de la méthode close(). L'ordre d'exécution
de plusieurs handlers est celui de leur déclaration. Tous les handlers
sont exécutés même si un handler précédent à lever une exception.

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

Les opérations map() et flatMap() servent toutes les deux à


réaliser des transformations mais il existe une différence
majeure :

 La méthode map() renvoie un seul élément


 La méthode flatMap() renvoie un Stream qui
peut contenir zéro, un ou plusieurs éléments
 L'exemple ci-dessous créé une paire de valeur (valeur -1
et valeur) à partir d'une collection de nombres. La valeur
retournée est ainsi une List de List d'Integer.

Exemple ( code Java 8 ) :


1.List<Integer> nombres = Arrays.asList(1, 3, 5, 7, 9);
2.List<List<Integer>> tuples =
3.nombres.stream()
4..map(nombre -> Arrays.asList(nombre - 1, nombre))
5..collect(Collectors.toList());
6.System.out.println(tuples);

Résultat :
1.[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

 Le même exemple que précédemment mais utilisant une


opération flatMap() renvoie une List d'Integer dont
toutes les valeurs ont été mises à plat.

Exemple ( code Java 8 ) :


1.List<Integer> nombres = Arrays.asList(1, 3, 5, 7, 9);
2.List<Integer> nombresDesTuples =
3.nombres.stream()
4..flatMap(nombre -> Arrays.asList(nombre - 1, nombre).stream())
5..collect(Collectors.toList());
6.System.out.println(nombresDesTuples);

Résultat :
1.[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
La différence entre une collection et un
Stream

une collection permet de stocker cette séquence d'éléments et un Stream permet


d'exécuter des opérations sur cette séquence d'éléments.

Bien que les Collections et les Streams semblent avoir des similitudes, ils ont des
objectifs différents :

 Les collections permettent de gérer et de récupérer des données qu'elles stockent


 Les Streams assurent l'exécution lazy de traitements déclarés sur des données
d'une source

Les Streams diffèrent donc de plusieurs manières des Collections :

 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

La nouvelle API de gestion des dates de Java 8


Dans cette nouvelle API, deux conceptions du temps sont implémentées :

 Temps machine (nombre de seconde depuis le premier janvier 1970) ou timestamp ;


 Temps humain (plusieurs champs, jour - mois - année, ...)

- objets LocalDateTime et LocatDate .

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

public class DateTimeManipulation {


public static void main(String[] args) {

//Le 25 Décembre 2018 a 13H37

LocalDateTime ldt = LocalDateTime.of(2018, Month.DECEMBER, 25, 13, 37, 0);

System.out.println("Date de référence : " + ldt);

//Utilisation de l'objet ChronoUnit pour changer l'objet

System.out.println("+1 semaine : " + ldt.plus(1, ChronoUnit.WEEKS));

System.out.println("+2 mois : " + ldt.plus(2, ChronoUnit.MONTHS));

System.out.println("+3 ans : " + ldt.plus(3, ChronoUnit.YEARS));

System.out.println("+4 heures : " + ldt.plus(4, ChronoUnit.HOURS));

System.out.println("-5 secondes : " + ldt.minus(5, ChronoUnit.SECONDS));

System.out.println("-38 minutes : " + ldt.minusMinutes(38));

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;

public class PeriodDurationTest {

public static void main(String[] args) {

//Toujours notre 25 Décembre 2018 a 13H37


LocalDateTime ldt = LocalDateTime.of(2018, Month.DECEMBER, 25, 13, 37, 0);

LocalDateTime ldt2 = ldt.plus(3, ChronoUnit.YEARS);

LocalDateTime ldt3 = ldt.minusMinutes(1337);

Period p = Period.between(ldt.toLocalDate(), ldt2.toLocalDate());

Duration d = Duration.between(ldt.toLocalTime(), ldt3.toLocalTime());

System.out.println("Période : " + p);

System.out.println("Durée : " + d.getSeconds());

System.out.println("Ecart en jour : " + ChronoUnit.DAYS.between(ldt, ldt2));

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

public class TemporalAdjustersTest {


public static void main(String[] args) {

//Toujours notre 25 Décembre 2018 a 13H37

LocalDate ldt = LocalDate.of(2018, Month.DECEMBER, 25);

//Le prochain samedi

LocalDate prochainSamedi = ldt.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));

System.out.println(prochainSamedi);

//Le troisième mardi du mois suivant

//On ajoute un mois à notre date

ldt = ldt.plus(1, ChronoUnit.MONTHS);

//On en créer une nouvelle au premier jour du mois

LocalDate ldt2 = LocalDate.of(ldt.getYear(), ldt.getMonth(), 1);

//On avance de trois mardi

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.

Les objets ZoneId et ZoneDateTime


Vous l'aurez sans doute deviné mais, pour pouvoir récupérer une heure et une date en fonction
d'un fuseau horaire nous allons devoir spécifier ce fuseau. Heureusement, la classe ZoneId
contient un objet de type Map qui contient tous les fuseau que nous pouvons utiliser.

import java.time.ZoneId;

import java.time.ZonedDateTime;

import java.util.Map;

public class TestZone {

public static void main(String[] args) {

Map<String, String> maps = ZoneId.SHORT_IDS;

maps.values().stream().forEach((x) -> {System.out.println(x + " -- " +


ZoneId.of(x).getRules());});

//Et connaître notre fuseau

System.out.println("");

System.out.println("Fuseau horaire courant : "+ZoneId.systemDefault());

System.out.println("Règle appliquer aux heures : " +


ZoneId.systemDefault().getRules());

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]

Fuseau horaire courant : Europe/Paris


Règle appliquer aux heures : ZoneRules[currentStandardOffset=+01:00]
Maintenant que nous connaissons cela, nous allons pouvoir jouer avec des dates. Dans
l'exemple qui suit, j'ai utilisé trois fuseau différents : Europe/Paris,
Asia/Tokyo etAmerica/Anchorage.

Voici un code qui parse une date/heure et qui nous donne sa valeur en fonction d'un fuseau
horaire :

LocalDateTime ldt = LocalDateTime.parse("2018-01-01T01:33:00");

List<ZoneId> lzi = Arrays.asList(

ZoneId.of("Europe/Paris"),

ZoneId.of("Asia/Tokyo"),

ZoneId.of("America/Anchorage")

);

lzi .stream()

.forEach((x) -> {

System.out.println(x + " : \t" + ldt.atZone(x).toInstant());

});

J'ai volontairement choisi un moment ou la date est complètement différente en fonction du


fuseau, ainsi, nous obtenons ceci :
Europe/Paris : 2018-01-01T00:33:00Z
Asia/Tokyo : 2017-12-31T10:33:00Z
America/Anchorage : 2018-01-01T16:33:00Z

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?

Questions et réponses d'entrevue de Java 8


Dans cette section, nous allons reprendre chaque question de la section précédente et y
répondre avec une description détaillée. Si vous avez besoin d'informations supplémentaires et
d'exemples, consultez les publications précédentes de Java SE 8 disponibles dans JournalDEV.

Pourquoi avons-nous encore besoin de passer à Java?


Oracle Corporation a introduit de nombreux nouveaux concepts dans Java SE 8 pour présenter
les avantages suivants:

 Pour utiliser efficacement les processeurs multicœurs actuels

Récemment, nous avons pu observer des changements drastiques dans le matériel. De


nos jours, tous les systèmes utilisent des processeurs multicœurs (2,4,8,16 cœurs, etc.)
pour déployer et exécuter leurs applications. Nous avons besoin de nouvelles constructions
de programmation en Java pour utiliser efficacement ces processeurs multicœurs afin de
développer des applications hautement simultanées et hautement évolutives.

 Pour utiliser les fonctionnalités de FP

Oracle Corporation a introduit de nombreux concepts de FP (Functional Programming)


dans le cadre de Java SE 8 pour tirer parti des avantages de la FP.

Java SE 8 New Features?


 Lambda Expressions
 Functional Interfaces
 Stream API
 API Date et heure
 Méthodes d'interface par défaut et méthodes statiques
 Spliterator
 Méthode et références du constructeur
 Collections API Enhancements
 Améliorations des utilitaires de concurrence
 Améliorations apportées au framework Fork / Join
 Itération interne
 Opérations sur les tableaux et collectes parallèles
 Optionnel
 Annotations de type et annotations répétables
 Méthode Paramètre Réflexion
 Encodage et décodage Base64
 Améliorations IO et NIO2
 Nashorn JavaScript Engine
 Améliorations de javac
 Changements JVM
 Java 8 Compact Profiles: compact1,compact2,compact3
 JDBC 4.2
 JAXP 1.6
 Java DB 10.10
 La mise en réseau
 Changements de sécurité

Avantages de Java SE 8 Nouvelles fonctionnalités?


Les nouvelles fonctionnalités de Java SE 8 offrent les avantages suivants:

 Code plus concis et lisible


 Plus de code réutilisable
 Code plus testable et maintenable
 Code hautement concourant et hautement évolutif
 Écrire un code parallèle
 Écrire comme des opérations de base de données
 Meilleures applications de performance
 Code plus productif

Qu'est-ce que l'expression Lambda?


Lambda Expression est une fonction anonyme qui accepte un ensemble de paramètres d'entrée
et renvoie les résultats.

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.

Quelles sont les trois parties d'une expression


Lambda? Quel est le type d'expression Lambda?
Une expression lambda contient 3 parties:

 Liste de paramètres

Une expression Lambda peut contenir zéro ou un ou plusieurs paramètres. C'est optionnel.

 Opérateur Lambda Arrow

“->” est connu sous le nom d’opérateur Lambda Arrow. Il sépare la liste des paramètres et
le corps.

 Corps d'expression lambda

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 .

Exemple: - Quel est le type de l'expression Lambda suivante?

() -> System.out.println("Hello World");


Cette expression lambda n'a pas de paramètres et ne renvoie aucun résultat. Donc, son type est
“java.lang.Runnable” Interface fonctionnelle.

Qu'est-ce qu'une interface fonctionnelle? Qu'est-ce que SAM


Interface?
Une interface fonctionnelle est une interface qui contient une et une seule méthode
abstraite. L'interface fonctionnelle est également appelée interface SAM car elle ne contient
qu'une méthode abstraite.

SAM Interface signifie Single Abstract Method Interface. L'API Java SE 8 a défini de nombreuses
interfaces fonctionnelles.

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?
Oui, il est possible de définir nos propres interfaces fonctionnelles. Nous utilisons l'annotation
@FunctionalInterface de Java SE 8 pour marquer une interface en tant qu'interface fonctionnelle.
Nous devons suivre ces règles pour définir une interface fonctionnelle:

 Définir une interface avec une et une seule méthode abstraite.


 Nous ne pouvons pas définir plus d’une méthode abstraite.
 Utilisez l'annotation @FunctionalInterface dans la définition d'interface.
 Nous pouvons définir n'importe quel nombre d'autres méthodes, telles que les méthodes
par défaut, les méthodes statiques.
 Si nous substituons la méthode de la classe java.lang.Object à une méthode abstraite, qui
ne compte pas comme une méthode abstraite.

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?
Il n'est pas obligatoire de définir une interface fonctionnelle avec l'annotation
@FunctionalInterface. Si nous ne voulons pas, nous pouvons omettre cette
annotation. Cependant, si nous l’utilisons dans la définition de l’interface fonctionnelle, le
compilateur Java oblige à n’utiliser qu’une et une seule méthode abstraite au sein de cette
interface.

Pourquoi avons-nous besoin d'interfaces fonctionnelles? Le type d'une expression lambda de


Java SE 8 est une interface fonctionnelle. Partout où nous utilisons des expressions Lambda,
cela signifie que nous utilisons des interfaces fonctionnelles.

Quand choisissons-nous l'API Java 8 Stream? Pourquoi


devons-nous utiliser l'API Java 8 Stream dans nos projets?
Lorsque notre projet Java souhaite effectuer les opérations suivantes, il est préférable d'utiliser
l' API Java 8 Stream pour obtenir de nombreux avantages:

 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».

L'avantage principal de facultatif est:

 Il est utilisé pour éviter les contrôles nuls.


 Il est utilisé pour éviter “NullPointerException”.

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?
Type Inference signifie déterminer le Type par compilateur au moment de la compilation.
Ce n’est pas une nouvelle fonctionnalité de Java SE 8. Il est également disponible dans Java 7 et
avant Java 7.

Avant Java 7: -
Explorons les tableaux Java. Définissez une chaîne de tableau avec les valeurs indiquées ci-
dessous:

String str[] = { "Java 7", "Java 8", "Java 9" };


Ici, nous avons attribué des valeurs de chaîne à droite, mais nous n’avons pas défini son type. Le
compilateur Java en déduit automatiquement le type et crée un String of Array.

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.

Map<String,List<Customer>> customerInfoByCity = new HashMap<>();


Ici, nous n'avons pas défini les informations de type à droite, mais simplement défini l'opérateur
diamant de Java SE 7 “”.

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.

ToIntBiFunction<Integer,Integer> add = (a,b) -> a + b

Quels sont les principaux inconvénients de l'itération


externe?
L'itération externe présente les inconvénients suivants:

 Nous devons écrire du code en style impératif.


 Il n'y a pas de séparation claire des responsabilités. Couplage étroit entre le code «Ce qu’il
faut faire» et «Comment faire».
 Code moins lisible.
 Plus de code Verbose et Boilerplate.
 Nous devons itérer les éléments uniquement dans l'ordre séquentiel.
 Il ne prend pas correctement en charge la concurrence et le parallélisme.

Quels sont les principaux avantages de l'itération interne par


rapport à l'itération externe?
Comparé à une itération externe, une itération interne présente les avantages suivants:
 Comme il suit le style de programmation fonctionnelle, nous pouvons écrire du code
déclaratif.
 Code plus lisible et concis.
 Evite l'écriture de code Verbose et Boilerplate
 Pas besoin d'itérer les éléments dans l'ordre séquentiel.
 Il supporte correctement la concurrence et le parallélisme.
 Nous pouvons écrire du code parallèle pour améliorer les performances des applications.
 Séparation claire des responsabilités. Couplage lâche entre le code «Ce qu’il faut faire» et
«Comment faire».
 Nous n'avons besoin d'écrire que du code «Ce qu'il faut faire» et l'API Java prend soin du
code «Comment faire».

Quel est le principal inconvénient de l'itération interne par


rapport à l'itération externe?
Comparée à une itération externe, une itération interne présente un inconvénient majeur:

 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.

Quel est le principal avantage de l'itération externe par


rapport à l'itération interne?
Par rapport à l'itération interne, l'itération externe présente un avantage majeur:

 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.

Quand devons-nous utiliser l'itération interne? Quand


devons-nous utiliser l'itération externe?
Nous devons comprendre les situations pour utiliser l'itération interne ou l'itération externe.

 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.

Est-il possible de fournir des implémentations de méthodes


dans les interfaces Java? Si possible, comment les
fournissons-nous?
En Java 7 ou version antérieure, il n'est pas possible de fournir des implémentations de
méthodes dans les interfaces. Java 8, c'est possible.

Dans Java SE 8, nous pouvons fournir des implémentations de méthodes dans des interfaces en
utilisant les deux nouveaux concepts suivants:

 Méthodes par défaut


 Méthodes statiques

Qu'est-ce qu'une méthode par défaut? Pourquoi avons-nous


besoin des méthodes par défaut dans les interfaces Java 8?
Une méthode par défaut est une méthode qui est implémentée dans une interface avec le mot
clé «default». C'est une nouvelle fonctionnalité introduite dans Java SE 8.

Nous avons besoin de méthodes par défaut pour les raisons suivantes:

 Cela nous permet de fournir l'implémentation de la méthode dans les interfaces.


 Ajouter de nouvelles fonctionnalités à l'interface sans rompre les classes qui implémentent
cette interface.
 Fournir une fonctionnalité de compatibilité ascendante élégante.
 Pour faciliter l'extension de la fonctionnalité existante.
 Pour faciliter le maintien de la fonctionnalité existante.

Qu'est-ce qu'une méthode statique? Pourquoi avons-nous


besoin de méthodes statiques dans les interfaces Java 8?
Une méthode statique est une méthode utilitaire ou une méthode auxiliaire associée à une classe
(ou une interface). Il n'est associé à aucun objet.

Nous avons besoin de méthodes statiques 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.

Expliquez les problèmes de l'ancienne API Java Date? Quels


sont les avantages de l'API Date et heure de Java 8 par
rapport à l'API Old Date et à l'API Joda Time?
L'API Java OLD Java Date signifie l'API Date disponible avant Java SE 8, soit Date, Calendrier,
SimpleDateFormat, etc.

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.

 La plupart des API sont obsolètes.


 Moins de lisibilité.
 java.util.Date est mutable et non thread-safe.
 java.text.SimpleDateFormat n'est pas Thread-Safe.
 Moins de performance.
L'API Date et heure de Java SE 8 présente les avantages suivants par rapport à l'API OLD Date
de Java.

 Très simple à utiliser.


 Syntaxe lisible par l'homme qui est plus lisible.
 Toutes les API sont thread-safe.
 Meilleure performance.

Pourquoi avons-nous besoin d'une nouvelle API de date et


heure dans Java SE 8. Expliquez comment l'API de données
et d'heure de Java SE 8 résout les problèmes de l'ancienne
API de date Java.
Nous avons besoin de l’API Date et heure de Java 8 pour développer des applications Java
hautement performantes, sécurisées pour les threads et hautement évolutives.

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.

Qu'est-ce que l'héritage multiple? Comment Java 8 prend-il


en charge l'héritage multiple?
Héritage multiple signifie qu'une classe peut hériter ou étendre des caractéristiques et des
fonctionnalités de plusieurs classes parentes.
Dans Java 7 ou version antérieure, l'héritage multiple n'est pas possible car Java suit la règle
«Une classe doit étendre une et une seule classe ou une classe abstraite». Cependant, il est
possible de fournir plusieurs héritages d'implémentation à l'aide d'interface, car Java suit la règle
«Une classe peut étendre un nombre illimité d'interfaces».

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.

Quel est le problème de diamant dans l'héritage? Comment


Java 8 résout ce problème?
Un problème de diamant est un problème d'héritage multiple. En Java, cela se produit lorsqu'une
classe étend plusieurs interfaces ayant la même implémentation de méthode (méthode par
défaut).

Vous aimerez peut-être aussi