:: Enseignements :: Licence :: L3 :: 2013-2014 :: Programmation Objet avec Java ::
[LOGO]

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

  1. Rappeler pourquoi la ligne ci-dessous ne compile pas
            ArrayList<int> list = new ArrayList<>();
          

    Pourquoi list.add(i); compile ?
  2. 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.
  3. 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.
  4. 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.
  5. Comment faire en sorte que l'on puisse aussi créer des IntegerFlow avec des HashSet<Integer> en ayant un code unique pour IntegerFlow?
  6. 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.

  1. 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 ?
  2. 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.
  3. 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.

  1. É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.
  2. Écrire le code des classes Flow et MappedFlow.
  3. 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 ?
  4. 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.