:: Enseignements :: Master :: M1 :: 2015-2016 :: Java Avancé ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) |
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.
-
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.
-
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.
-
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);
-
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 !
-
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> ?
-
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é.
-
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 ?
-
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 :(
-
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 !
-
Vérifier avec les tests unitaires (en les dé-commentant) que votre implantation de l'itérateur fonctionne correctement.
-
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.
-
On peut remarquer que Iterable definie déjà une méthode forEach, donc on peut supprimer celle
que nous avons écrite.
-
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 ?
© Université de Marne-la-Vallée