:: Enseignements :: ESIPE :: E4INFO :: 2023-2024 :: Java Avancé ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) | Examen de Java Avancé |
Le but de ce TP noté est d'implanter une classe UnionSet qui est un Set
qui se comporte comme l'union de deux Set passés à la construction
sans pour autant créer les éléments de l'union
Vos sources Java produites pendant l'examen devront être placées sous le répertoire EXAM
de votre compte ($HOME) (qui est vide dans l'environnement de TP noté).
Sinon, elles ne seront pas récupérées.
Vous avez le droit de lire le sujet jusqu'au bout, cela vous donnera une bonne idée de là où on veut aller !
Exercice 1 - UnionSet
On crée un
UnionSet à partir de deux
Set que l'on recopie pour qu'ils soient non-mutables
(s'ils ne sont pas déjà non-mutables).
UnionSet est une classe paramétrée par le type des éléments
contenus. Cette classe ne supporte pas les éléments
null.
La classe
UnionSet
-
possède une méthode size qui renvoie le nombre d'éléments de l'UnionSet
possède une méthode contains(item) qui indique si l'item fait partie des éléments de l'UnionSet
-
possède une méthode d'affichage
-
permet de faire une boucle sur ses éléments
-
possède une méthode stream.
L'implantation de
UnionSet suit les deux principes suivants
-
Un élément est dans l'UnionSet si il appartient à un des deux Set
-
Pour le parcours, on parcourt d'abord les éléments du premier Set puis les éléments
du second Set qui ne sont pas déjà dans le premier Set.
Voici un exemple d'utilisation
Set<String> set1 = Set.of("1");
Set<String> set2 = Set.of("1", "2");
UnionSet<String> unionSet = UnionSet<String>.of(set1, set2);
System.out.println(unionSet.contains("1")); // true
System.out.println(unionSet.contains("2")); // true
System.out.println(unionSet.size()); // 2
System.out.println(unionSet); // [1, 2]
for(String s: unionSet) {
System.out.println(s);
} // "1"
// "2"
System.out.println(unionSet.stream().toList()); // [1, 2]
Dans l'exemple, on crée un
UnionSet avec la méthode
of et les deux ensembles.
La méthode
contains(item) renvoie vrai si l'item est dans l'
UnionSet donc
si l'item est soit dans le
set1 soit dans le
set2.
La méthode
size() renvoie le nombre d'éléments dans l'
UnionSet.
Il est possible d'obtenir les valeurs de l'
UnionSet soit en utilisant la boucle habituelle de Java,
soit en utilisant un
Stream.
Des tests unitaires correspondant à l'implantation sont ici :
UnionSetTest.java
Note : comme on utilise les tests unitaires JUnit sans Maven, dans la configuration de votre projet, il faut ajouter
la librairie JUnit 5, soit à partir du fichier
DocumentTest.java, en cliquant sur l'annotation
@Test et en sélectionnant le quickfix "Fixup project ...", soit en sélectionnant les "Properties" du projet
(avec le bouton droit de la souris sur le projet) puis en ajoutant la librairie JUnit 5 (jupiter) au ClassPath.
-
On souhaite créer la classe UnionSet avec deux champs set1 et set2.
La création d'un UnionSet se fait en utilisant la méthode of(set1, set2).
Chaque champ stocke soit le Set pris en paramètre de la méthode of
si celui-ci est non-mutable, soit une version non-mutable de celui-ci.
On souhaite de plus implanter la méthode contains(item) comme décrite ci-dessus.
Écrire la classe UnionSet et les méthodes of et contains.
Vérifier que les tests marqués "Q1" passent.
-
On souhaite maintenant pouvoir afficher les éléments de l'UnionSet avec le même format
que les listes (non, cela ne veut pas dire que l'on va créer une liste intermédiaire, juste
que l'on veut que l'affichage soit le même qu'une liste).
On vous demande de créer un Stream qui est le résultat de la concaténation
du Stream sur le premier Set avec un Stream sur le second Set pour lequel
on garde les éléments qui ne sont pas dans le premier Set
et de l'utiliser pour effectuer l'affichage.
Modifier le code de la classe UnionSet pour permettre l'affichage de celle-ci.
Vérifier que les tests marqués "Q2" passent.
Rappel : il existe une méthode Stream.concat(stream1, stream2).
-
Avant de faire la suite, on va créer plusieurs méthodes publiques pour nous aider.
Dans un premier temps, on veut créer une méthode concat(iterator1, iterator2) qui prend
en paramètre deux itérateurs et renvoie un nouvel itérateur qui, si on l'utilise, parcourt les éléments
du premier itérateur puis les éléments du second itérateur.
Écrire la méthode publique concat().
Vérifier que les tests marqués "Q3" passent.
Conseil : l'itérateur se comporte comme le premier itérateur s'il n'est pas vide
et comme le deuxième sinon.
-
On veut aussi ajouter une méthode filterOut(iterator, function) qui prend en paramètre
un itérateur et une fonction et renvoie un itérateur. La fonction prend en paramètre une valeur
et renvoie vrai si la valeur NE doit PAS faire partie de l'itérateur résultant.
Pour vous aider, vous pouvez écrire une méthode privée computeNext() qui renvoie
le prochain élément que doit renvoyer l'itérateur ou null sinon.
Écrire la méthode publique filterOut.
Vérifier que les tests marqués "Q4" passent.
-
On souhaite maintenant pouvoir parcourir les éléments de l'UnionSet avec la boucle
habituelle de Java. En termes d'implantation, nous allons utiliser les deux itérateurs écrits
précédemment pour nous aider.
Modifier la classe UnionSet pour que l'on puisse parcourir les éléments avec une boucle.
Vérifier que les tests marqués "Q5" passent.
-
On cherche maintenant à implanter la méthode size.
On peut remarquer que le calcul la taille, en pire cas, est linéaire. Mais on peut utiliser une astuce.
Les deux Set sont non-modifiables, donc la taille ne change pas, il suffit de la calculer
une seule fois et de s'en souvenir. Bien sûr, on ne va faire le calcul que si cela est nécessaire,
la première fois que l'on demande la valeur, ce que l'on appelle faire une évaluation paresseuse
(lazy evaluation en anglais).
Une fois que la méthode size est implantée, il est facile de faire en sorte que
la classe UnionSet implante l'interface Set.
Implanter la méthode size de façon paresseuse et faite en sorte que
la classe UnionSet implante l'interface Set
Vérifier que les tests marqués "Q6" passent.
Note : en bonus, on peut faire le calcul de la taille sans boucle.
-
Dans le but d'implanter de façon efficace la méthode stream(), dans un premier
temps, on va créer une méthode publique concatFilterOutSpliterator(spliterator1, spliterator2, function)
qui prend en paramètre deux Spliterator et une fonction et renvoie un Spliterator.
La fonction est appelée avec les éléments du second Spliterator (l'un après l'autre) et renvoie vrai si l'élément
ne doit pas être dans le Spliterator résultant.
Implanter la méthode publique concatFilterOutSpliterator().
Vérifier que les tests marqués "Q7" passent.
-
On souhaite maintenant écrire la méthode stream qui renvoie les éléments de l'UnionSet.
Écrire la méthode stream().
Vérifier que les tests marqués "Q8" passent.
Note : pour l'instant, on ne vous demande pas que le Stream soit parallélisable.
-
En bonus, on peut remarquer qu'il est possible de rendre le Stream parallélisable.
Faites les changements qui s'imposent.
Vérifier que les tests marqués "Q9" passent.
© Université de Marne-la-Vallée