:: Enseignements :: Master :: M1 :: 2025-2026 :: Java Avancé ::
[LOGO]

Examen de Java Avancé 2025 - TP intermédiaire


Le but de ce TP noté est d'implanter une classe PartitionVec qui représente une Collection capable de se partitionner, c'est à dire de se couper en deux virtuellement en fonction d'un critère.

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.

Tout document papier est proscrit.
La javadoc 25 est https://igm.univ-mlv.fr/~juge/javadoc-25/.
Les seuls documents électroniques autorisés sont les supports de cours à l'URL http://igm.univ-mlv.fr/~forax/ens/java-avance/cours/pdf/.

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 - PartitionVec

Un PartitionVec est une collection d'éléments non null qui stocke les éléments dans un tableau. Cette collection ne peut que grandir (pas de suppression) et se couper virtuellement en deux, suite à un appel à la méthode partition()
Voici un exemple d'utilisation :
PartitionVec<Integer> vec = new PartitionVec<Integer>();
vec.add(2);
vec.add(3);
vec.add(7);
vec.add(4);

List<Integer> result = vec.partition(n -> n % 2 == 0);

IO.println(result);   // [2, 4]
     
La méthode add(element) permet d'ajouter des éléments à la structure de données.
La méthode partition(function) déplace les éléments de telle façon que ceux pour lesquels function renvoie vrai se retrouvent en tête dans le tableau (donc à gauche) et ceux pour lesquels elle renvoie faux se retrouvent à la fin dans le tableau.
Avec notre exemple, une fois la méthode partition appelée, le tableau à l'intérieur de notre PartitionVec est [2, 4, 7, 3], car 2 et 4 sont pairs et 7 et 3 sont impairs.
La liste renvoyée par partition est une vue des éléments qui sont vrais pour la fonction prise en paramètre, ce qui donne [2, 4] dans notre exemple.

La classe PartitionVec possède les méthodes suivantes :
  • un constructeur sans paramètre qui permet de créer un PartitionVec vide,
  • une méthode add(element) qui permet d'ajouter un élément,
  • une méthode size qui renvoie le nombre d'éléments,
  • une méthode toString pour l'affichage,
  • une méthode partition(function) qui permet de partitionner la collection comme décrit ci-dessus.

On peut noter qu'il ne doit pas être possible de supprimer un élément du PartitionVec.

Des tests unitaires correspondant à l'implantation sont ici : PartitionVecTest.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 PartitionVecTest.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.

  1. Dans un premier temps, on souhaite implanter le constructeur et les méthodes add et size.
    Lors de la création d'un PartitionVec, le nombre de cases allouées doit être 4. Ce tableau s'agrandit dynamiquement lorsque celui-ci est plein en multipliant la capacité par 2.
    Créer la classe PartitionVec ainsi que les méthodes demandées.
    Vérifier que les tests unitaires marqués Q1 passent.

  2. On souhaite pouvoir afficher le contenu d'un PartitionVec.
    Écrire la méthode d'affichage en utilisant un Stream.
    Vérifier que les tests unitaires marqués Q2 passent.

  3. On veut maintenant implanter la méthode partition(function).
    Voici l'algorithme que nous allons utiliser :
           On utilise deux indices : i initialisé à 0 (à gauche) et limit initialisé à la taille (à droite)
    
           tant qu'il reste des éléments,
             on regarde l'élément à la case i du tableau
    
             si la fonction renvoie vrai pour cet élément,
               on le laisse à gauche et on incrémente i
    
             si la fonction renvoie faux pour cet élément,
               on veut l'envoyer à droite, pour cela
               on échange (swap) cet élément avec l'élément juste avant limit,
               on décrémente limit
          

    De plus, partition(function) doit renvoyer une liste qui est une vue des éléments à gauche.
    Vérifier que les tests unitaires marqués Q3 passent.
    Note : en Java, il existe les méthodes Arrays.asList qui permet de voir un tableau comme une liste et List.subList() qui permet de voir une partie d'une liste. Dans les deux cas, les List renvoyées sont des vues.

  4. On veut que la liste renvoyée par partition(function) soit non-modifiable, hors Arrays.asList renvoie une List modifiable. Il faut donc modifier votre implantation.
    Écrire une classe locale à la méthode partition(function) qui implante une vue non modifiable.
    Vérifier que les tests unitaires marqués Q4 passent.
    Rappel : pour implanter une List non modifiable, on peut utiliser la classe abstraite AbstractList qui implante déjà une partie du travail.

  5. Comme la méthode partition renvoie une vue, celle-ci n'est valide que jusqu'au prochain appel à partition() qui va re-mélanger les éléments. Pour éviter une mauvaise utilisation de la vue, on va empêcher d'accéder aux éléments de la vue si jamais la méthode partition(function) est appelée de nouveau.
    Pour faire cela, on va utiliser un compteur dans PartitionVec que l'on incrémente à chaque fois que la méthode partition(fonction) est appelée et dans la vue, on va tester que la valeur du compteur n'a pas changé.
    Implanter ce mécanisme.
    Vérifier que les tests unitaires marqués Q5 passent.
    Note : ici, on ne fait pas dans la finesse, donc on va empêcher l'accès même dans le cas où lorsque l'on appelle partition(function), les éléments ne changeraient de place.

  6. On souhaite maintenant ajouter une méthode otherPartition() que l'on appelle sur le résultat de la méthode partition(function) et qui renvoie toujours une vue mais de l'autre partie du tableau.
    var vec = new PartitionVec<Integer>();
    vec.add(2);
    vec.add(3);
    vec.add(7);
    vec.add(4);
    
    var evenPartition = vec.partition(n -> n % 2 == 0);
    var oddPartition = evenPartition.otherPartition();
    
    IO.println(evenPartition);  // [2, 4]
    IO.println(oddPartition);   // [7, 3]
          

    Modifier votre implantation en conséquence.
    Vérifier que les tests unitaires marqués Q6 passent. Si vous n'y arrivez pas, passez cette question et la suivante.

  7. Si vous ne l'avez pas déjà fait, si l'on appelle otherPartition() sur une partition déjà demandée avec otherPartition(), on doit retomber sur une vue de la partition initiale.
    Modifier votre code pour avoir ce comportement.
    Vérifier que les tests unitaires marqués Q7 passent.

  8. On veut maintenant que notre classe PartitionVec soit une collection, i.e. implante l'interface Collection.
    Modifier votre code pour que la classe PartitionVec implante Collection.
    On veut de plus, que le code suivant fonctionne
    var vec = new PartitionVec<String>();
    vec.add("alpha");
    vec.add("beta");
    
    for(var element : vec) {
      vec.add(element);
    }
    
    IO.println(vec);   // [alpha, beta, alpha, beta]
          

    Vérifier que les tests unitaires marqués Q8 passent.
    Attention : PartitionVec ne doit pas permettre les suppressions !

  9. Enfin, on veut que si un itérateur est créé sur un PartitionVec et que l'on appelle ensuite partition(function) sur ce PartitionVec alors l'itérateur ne doit plus pouvoir renvoyer d'élément (étant donnant qu'ils ont été re-mélangés).
    Implanter ce comportement.
    Vérifier que les tests unitaires marqués Q9 passent.