:: Enseignements :: Master :: M1 :: 2015-2016 :: Java Avancé ::
[LOGO]

Classe interne, classe anonyme, lambda et iterateur.


Le but de ce TD est d'implanter une séquence d'élements chainées anisi que differentes façons de la parcourir.
La séquence est définie par une interface et deux classes.
  • L'interface Seq contient les opérations communes (pour l'instant aucune !)
  • La classe Nil définie la dernière partie de la séquence et est donc une structure vide.
  • La classe Cons définie un maillon de la séquence et stocke un élement (de type Object) et une référence sur la suite de la séquence
Voici un main d'exemple d'utilisation des différentes classes

Exercice 1 - Better Seq

Nous allons maintenant améliorer l'implantation.

  1. Pour l'instant, il est possible de créer une séquence mais pas d'utiliser les valeurs stockées dans celle-ci. Ajouter une méthode forEach qui prend un java.util.function.Consumer en paramètre et qui appel le consommateur pour tous les éléments dans la séquence.
    Quelle doit être la signature exacte de la méthode forEach ?
    Implanter la méthode forEach avec un algorithme récursif simple et tester en modifiant le main pour afficher les valeurs sur la sortie standard.
  2. En fait, les classes Cons et Nil ne devraient pas être publiques car ceux sont des détails d'implantation, elles n'ont pas de méthode propres, juste celle de Seq.
    Déplacer Cons et Nil en tant que classes internes à l'intérieur de Seq et rappeler quelle est la différence entre une classe interne déclarée statique ou pas à l'intérieur d'une interface.
  3. Le main fait toujours référence directement à Cons et Nil montrant toujours les détails d'implantations, nous allons cacher cela en introduisant deux méthodes:
    • Une méthode statique nil() dans Seq qui permet de créer une séquence vide.
      On peut remarquer que cela ne sert à rien de re-créer une instance de Nil à chaque fois que l'on appel nil() car Nil est non mutable, une seule instance devrait suffire.
    • une méthode prepend qui permet d'ajouter un élement avant la séquence sur laquelle on appel la méthode.
      On peut remarquer qu'ajouter un Cons est indépendant du type de this et donc le code de la méthode prepend peut être définie une seule fois dans l"interface Seq sous forme de default method.
    A ce stade, le main suivant devrait fonctionner et ne plus montrer de détails d'implantation.
         Seq seq = Seq.nil();
         Seq seq2 = seq.prepend("world").prepend("hello");
         seq2.forEach(System.out::println);
        
  4. Mais bon, rien n'empêche un utilisateur d'écrire
          Seq seq2 = seq.prepend("world").prepend(3);
        
    mixant des éléments de types differents. Pour éviter cela, nous allons générifier Seq en ajoutant une variable de type correspondant au type de l'élément.
    Note: si vous avez un peu de mal avec l'implantation de la méthode nil, aller regarder dans le cours !
  5. Expliquer pourquoi le code suivant ne compile pas :(
         Seq<String> seq3 = Seq.nil().prepend("world");
        

    Comment faire pour que le compilateur voit bien Seq.nil() comme un Seq<String> ?
  6. Vérifier que votre implantation passe les tests unitaires suivant
    SeqTest.java.

Exercice 2 - Dead Awesome Seq

En fait, Seq peut être encore amélioré.

  1. On cherche à faire la somme d'entiers stockés dans une séquence mais le code suivant ne marche pas
          Seq<Integer> seq4 = Seq.nil();
          seq4 = seq4.prepend(3).preprend(2).prepend(1);
          
          int sum = 0;
          seq4.forEach(element -> sum += element);
        

    Pourquoi ?
    Comment faire pour qu'il marche en utilisant soit une classe locale soit un tableau d'entiers ?
  2. Pour éviter d'avoir un code moche et illisible, nous allons plutôt implanter un Iterator sur la séquence.
    Mais avant, modifiez l'implantation du forEach de Cons pour qu'elle soit itérative, c'est plus efficace mais le code est moins beau, comme on ne profite plus du polymorphisme, il faudra utiliser un affreux cast :(
  3. Ajouter une méthode iterator dans l'interface Seq, l'implantation pour Nil consistera à renvoyer un itérateur vide, cela tombe bien, il a déjà été implanté, Collections.emptyIterator(). Pour la méthode iterator de Cons, reprenez le code du forEach itératif et adaptez le.
    Dans notre cas, il n'est pas possible d'implanter la méthode remove de l'iterateur !
  4. Vérifier avec les tests unitaires (en les dé-commentant) que votre implantation de l'itérateur fonctionne correctement.
  5. On peut donc maintenant faire la somme des élements en utilisant une boucle while avec l'iterateur, mais on voudrait utiliser la boucle for(:).
          int sum = 0;
          for(Integer element: seq4) {
            sum += element;
          }
        

    Il faut pour cela que Seq soit Iterable. Faire les changements qui s'impose.
  6. On peut remarquer que Iterable definie déjà une méthode forEach, donc on peut supprimer celle que nous avons écrite.
  7. On peut remarquer que Seq est une fonctional interface, il est donc possible d'implanter Seq avec une lambda. Modifier le code de nil() pour utiliser une lambda pour implanter le code équivalent à Nil.
    Quel est l'avantage d'utiliser une lambda ici ?