:: Enseignements :: Licence :: L3 :: 2013-2014 :: Programmation Objet avec Java ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) |
Un TD lambda.
|
Le but de ce TD est de se familiariser avec la syntaxe et le concept de lambda.
Exercice 1 - IntegerFlow
On cherche à écrire une classe IntegerFlow permettant d'itérer
sur des éléments de façon interne puis de faire des transformations
sur les éléments en évitant de créer des structures de données
intermédiaires.
On cherche dans un premier temps, à écrire la classe
IntegerFlow
dans le package
fr.umlv.flow tel que le code suivant fonctionne.
YYY correspond au code d'une lambda qui prend
un entier et affiche celui-ci sur la sortie standard.
XXX correspond au type de la lambda.
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; i++) {
list.add(i);
}
IntegerFlow flow = IntegerFlow.create(list);
XXX lambda = YYY
flow.each(lambda);
-
Rappeler pourquoi la ligne ci-dessous ne compile pas
ArrayList<int> list = new ArrayList<>();
Pourquoi list.add(i); compile ?
-
Rappeler ce qu'est une factory method, puis implanter
la classe IntegerFlow ainsi que sa méthode
create sachant que la classe va juste stocker la liste.
-
On cherche à déterminer le type de XXX.
Rappeler ce qu'est une interface fonctionnelle.
Puis chercher dans le paquetage java.util.function
le nom de l'interface fonctionnelle qui doit être utilisée ici.
-
Sachant que la méthode each doit appeler la lambda
prise en paramètre pour chaque entier de la liste,
quel doit être le code de YYY pour que le code
ci-dessus fonctionne et affiche un entier par ligne sur
la sortie standard ?
Quel doit être le type du paramètre de each ?
Écrire la méthode each.
Vérifier que le code fonctionne avec l'exemple.
-
Comment faire en sorte que l'on puisse aussi créer des IntegerFlow
avec des HashSet<Integer> en ayant un code unique pour IntegerFlow?
-
On cherche maintenant à ce que le code suivant fonctionne
flow.map(i -> i * 2).each(System.out::println);
et affiche les entiers avec leur valeur multipliée par 2.
Expliquer ce que veut dire System.out::println.
Sachant que la méthode map prend en paramètre une
interface fonctionnelle Mapper dont vous devez écrire le code
(on appelle la méthode de Mapper, transform),
quelle doit être la signature de la méthode map et
le code de l'interface fonctionnelle Mapper?
Rappeler à quoi sert l'annotation @FunctionalInterface,
comment peut-on l'utiliser ici ?
Écrire un code pour map et vérifier qu'il fonctionne avec l'exemple. Attention, la méthode map ne doit pas modifier flow.
Exercice 2 - map paresseux
Le problème avec le code de la méthode
map de l'exercice précédent
est que si l'on enchaine plusieurs appels à
map successifs
le code va calculer beaucoup de collections intermédiaires pour
au final parcourir la dernière.
On veut écrire un code plus efficace qui, au lieu de calculer les collections
intermédiaires, sauvegarde les mappers dans des champs
et ne fait les transformations des mappers que lorsque
each est appelée.
On cherche, dans un premier temps, à ce que le code
flow.map(i -> i * 2).each(System.out::println);
fonctionne. L'idée est que la valeur retournée par
map
soit un
IntegerFlow qui stocke, en plus des éléments,
le mapper à appliquer.
-
Créer la classe MappedIntegerFlow qui est un IntegerFlow
qui stocke le mapper en tant que champ.
Pourquoi cette nouvelle classe ne doit-elle pas être public?
Quel doit être le code de each dans ce cas ?
-
On remarque que le code suivant ne fait pas ce qu'il faut
flow.map(i -> i * 2).map(i -> i + 1).each(System.out::println);
Expliquer pourquoi.
Implanter la solution permettant de corriger le problème.
-
Le code n'est pas très lisible, on voudrait extraire
de code qui fait la composition de fonctions du reste du code
en écrivant une méthode andThen sur l'interface
Mapper de telle façon que f.andThen(g).transform(x)
soit équivalent à g(f(x))
(on applique f à x puis on applique g sur le résutat).
Quelle est la déclaration de andThen dans Mapper ?
Implanter la méthode andThen et utiliser celle-ci pour simplifier
le code dans MappedIntegerFlow.
Exercice 3 - Générification de IntegerFlow
La classe
IntegerFlow ne fonctionne qu'avec des
Integer,
on voudrait écrire une nouvelle classe
Flow (et aussi
MappedFlow) qui fonctionne
avec n'importe quel objet.
Par exemple, le code suivant devrait fonctionner :
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0; i < 0; i++) {
list.add(i);
}
Flow<Integer> flow = Flow.create(list);
flow.map(i -> i * 2).map(i -> i + 1).each(System.out::println);
De plus, au lieu d'utiliser notre propre classe
Mapper,
il existe déjà dans le JDK une classe
java.util.function.Function
qui joue le même rôle, donc la méthode
map de
Flow
l'utilisera.
-
Écrire la déclaration de la classe paramétrée Flow.
Puis compléter avec la déclaration des méthodes each et map.
Note: on ne s'intéresse pour l'instant qu'au cas où la fonction prise en paramètre
de map a le même type de retour que le type du paramètre.
-
Écrire le code des classes Flow et MappedFlow.
-
Expliquer pourquoi le code suivant ne compile pas
flow.map(i -> Integer.toString(i)).each(System.out::println);
Comment modifier la signature de la méthode map pour que le code compile ?
-
Expliquer pourquoi faire hériter MappedFlow de Flow ne
permet plus d'implanter la méthode map avec la nouvelle signature.
Expliquer pourquoi transformer Flow en interface résout le problème.
Implanter le code et vérifier que l'exemple fonctionne.
Attention, il y a malheureusement un bug dans la version d'Eclipse installée
sur vos machines (le support de la version 8 de Java est en béta), il faut
indiquer à Eclipse le type de retour de map de cette façon
flow.<String>map(i -> Integer.toString(i)).each(System.out::println);
Le bug a été remonté et devrait être corrigé dans la prochaine version.
Vous pouvez vérifier que javac lui n'a pas de problème avec ce code.
© Université de Marne-la-Vallée