:: Enseignements :: ESIPE :: E4INFO :: 2020-2021 :: Java Avancé ::
[LOGO]

Examen de Java Avancé


Le but de ce TP noté est d'implanter une structure de données appelée TaggedBuffer qui permet d'indiquer que certains de ses éléments sont importants (tagged) et d'autres pas.
Un TaggedBuffer offre la possibilité de voir soit tous ses éléments, soit uniquement ceux qui sont importants. Pour cela, les opération définies sur un TaggedBuffer sont paramétrées par un booléen qui indique si il faut considérer uniquement les éléments importants ou non.

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.
Vous pouvez consulter la javadoc à travers Eclipse (onglet Javadoc), en utilisant jdoc dans un terminal Sinon la doc est là: /usr/local/apps/java15/docs/api/index.html.
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 - GapBuffer

La classe TaggedBuffer est un conteneur d'éléments muni d'une fonction qui indique, pour chaque élément, si celui-ci est important ou non. La fonction prend donc en paramètre un élément et renvoie un booléen true si l'élément est important ou false si l'élément n'est pas important.
La fonction, prise à la construction, doit être consistente, c'est à dire toujours renvoyer la même valeur (true ou false) pour un même élément.
La classe TaggedBuffer possède les opérations suivantes :
  • add(element) qui permet d'ajouter un élément.
  • size(boolean onlyTagged) qui indique le nombre d'éléments
    (en considérant uniquement les éléments importants si onlyTagged est vrai)
  • findFirst(boolean onlyTagged) qui renvoie le premier élément.
    (en considérant uniquement les éléments importants si onlyTagged est vrai)
  • forEach(boolean onlyTagged, ...) qui permet de parcourir les éléments.
    (en considérant uniquement les éléments importants si onlyTagged est vrai)
  • iterator(boolean onlyTagged) qui renvoie un itérateur pour cette structure de données.
    (en considérant uniquement les éléments importants si onlyTagged est vrai)
  • asTaggedList() qui renvoie une liste des éléments importants.
  • stream(boolean onlyTagged) qui renvoie un Stream des éléments.
    (en considérant uniquement les éléments importants si onlyTagged est vrai)

La classe TaggedBuffer est paramétrée par le type des éléments qu'elle contient et ces éléments ne peuvent pas être null.
Attention, lorsque l'on appelle une des méthodes qui prend onlyTagged en paramètre avec la valeur false, alors la fonction qui indique si un élement est important ou pas ne doit pas être appelée.

Voici un exemple d'utilisation de la classe TaggedBuffer.
    var buffer = new TaggedBuffer<Integer>(i -> i % 2 == 0);
    buffer.add(1);
    buffer.add(2);
    buffer.add(3);
    buffer.add(5);
    System.out.println(buffer.size(true));   // 1
    System.out.println(buffer.size(false));  // 4
     
La première ligne crée un TaggedBuffer avec une fonction qui considère tous les nombres pairs (les nombres divisibles par 2) comme importants. Donc la valeur 2 est une valeur importante, tandis que les valeurs 1, 3 et 5 ne sont pas des valeurs importantes.
Lorsque l'on demande la taille en considérant uniquement les éléments importants : size(true), alors la taille est 1 car seul le nombre 2 est important. Lorsque l'on demande la taille en considérant tous les éléments : size(false), la taille est 4.

Des tests unitaires correspondant à l'implantation sont ici : TaggedBufferTest.java.

  1. On souhaite créer la classe TaggedBuffer avec le constructeur et les méthodes add et size.
    • Les éléments doivent être stockés dans un tableau (pas une liste !!)
    • Pour l'instant, votre implantation n'aura pas plus de 4 éléments.
    • la méthode size doit avoir une implantation en temps constant (sans parcourir les éléments si vous préférerez).

    Vérifier que les tests unitaires marqués "Q1" passent.
  2. Modifier le code pour permettre permettre à votre implantation de supporter un nombre arbitraire d'éléments en agrandissant la taille du tableau d'un facteur 2 (toujours en commençant avec une taille de 4 par défaut) lorsque le tableau est plein.
    Vérifier que le test unitaire marqué "Q2" passe.
  3. On souhaite écrire une méthode findFirst qui renvoie le premier élément du TaggedBuffer. Si le paramètre onlyTagged est vrai, on considère uniquement les éléments importants, et sinon, on considère tous les éléments (important ou non).
    Il faut aussi gérer le fait qu'il peut ne pas y avoir de premier élément à considérer.
    Écrire la méthode findFirst(boolean onlyTagged) et vérifier que les tests unitaires marqués "Q3" passent.
  4. On souhaite écrire une méthode forEach qui en plus du booléen indiquant si l'on doit considérer uniquement les éléments importants, prend en second paramètre une fonction qui est appelée avec chaque élément à considérer du TaggedBuffer.
    Les éléments doivent être appelés dans le même ordre que l'ordre d'insertion dans le TaggedBuffer.
    Écrire la méthode forEach et vérifier que les tests unitaires marqués "Q4" passent.
  5. On souhaite maintenant écrire une méthode iterator qui, comme les méthodes précédentes, prend en paramètre un booléen indiquant si l'on doit considérer uniquement les éléments importants ou non et renvoie un itérateur permettant de parcourir les éléments considérés du TaggedBuffer dans l'ordre d'insertion.
    Si l'on ajoute des éléments dans le TaggedBuffer après l'appel à la méthode iterator, ceux-ci ne doivent pas être visibles par l'itérateur renvoyé précédemment.
    Écrire la méthode iterator(boolean onlyTagged) et vérifier que les tests unitaires marqués "Q5" passent.
    Note 1 : On peut remarquer que l'on connaît à l'avance le nombre d'éléments que l'on va parcourir, donc il y a une façon simple (mais ce n'est pas la seule) d'écrire le test permettant de savoir si il reste encore des éléments dans l'itérateur.
    Note 2 : En terme d'implantation, on vous demande d'écrire votre itérateur sous forme d'une classe anonyme. Tout autre implantation sera considérée comme hors sujet.
  6. On souhaite maintenant écrire une méthode asTaggedList qui renvoie une liste non modifiable des éléments qui sont importants. L'accès à un élément en utilisant cette liste doit se faire en temps constant.
    Si l'on ajoute des éléments dans le TaggedBuffer après l'appel à la méthode asTaggedList, ceux-ci ne doivent pas être visibles par la liste renvoyée précédemment.
    On ne souhaite pas directement stocker les éléments dans la liste car cela ferait plus de travail pour le Garbage Collector (il doit suivre toutes les références).On va donc plutôt stocker un tableau des index des éléments qui sont importants.
    Par exemple, avec le code suivant
        var buffer = new TaggedBuffer<String>(s -> s.charAt(0) != 'b');
        buffer.add("booz");   // index 0
        buffer.add("bar");    // index 1
        buffer.add("foo");    // index 2
        buffer.add("baz");    // index 3
        buffer.add("whizz");  // index 4
          
    Les éléments "foo" et "whizz" sont importants (car ils ne commencent pas par 'b') donc le tableau des index est [2, 4] car ce sont les valeurs d'index des éléments "foo" et "whizz".
    Écrire la méthode asTaggedList et vérifier que les tests unitaires marqués "Q6" passent.
    Note : il existe une classe java.util.AbstractList !
  7. On souhaite écrire une méthode stream qui prend en paramètre un booléen indiquant si l'on doit considérer uniquement les éléments importants ou non et renvoie un Stream de ces éléments. Pour cela, on vous demande d'écrire un Spliterator sous-jacent.
    L'ordre des éléments dans le Stream doit être l'ordre d'insertion.
    Si l'on ajoute des éléments dans le TaggedBuffer après l'appel à la méthode stream, ceux-ci ne doivent pas être visibles par le Stream renvoyé précédemment.
    Pour cette question, on va pour l'instant se limiter à une implantation d'un Spliterator qui ne sait pas se couper en deux.
    Écrire la méthode stream(boolean onlyTagged) et vérifier que les tests unitaires marqués "Q7" passent.
  8. Enfin, on souhaite finir l'implantation du Spliterator en ajoutant le support des Stream parallèles.
    Modifier votre implantation en conséquence et vérifier que les tests unitaires marqués "Q8" passent.
    Note : on peut remarquer que suivant si l'on considère uniquement les éléments importants ou non, les caractéristiques du Spliterator ne sont pas les mêmes.