:: Enseignements :: Master :: M1 :: 2022-2023 :: Java Avancé ::
[LOGO]

Structure de données persistante (fonctionnelle)


Le but de ce TP est de bien faire la différence entre les informations exposées publiquement par une classe et les informations utilisées par celle-ci en interne.
Nous allons implanter une classe fr.uge.seq.Seq qui représente une séquence (comme une liste) paresseuse non mutable d'éléments non null. Les éléments sont stockés en interne dans une liste non mutable.

Exercice 1 - Maven

Pour ce TP nous allons utiliser la même configuration Maven qu'habituellement.
        <project xmlns="http://maven.apache.org/POM/4.0.0"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

          <modelVersion>4.0.0</modelVersion>
          <groupId>fr.uge.seq</groupId>
          <artifactId>seq</artifactId>
          <version>0.0.1-SNAPSHOT</version>

          <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          </properties>

          <dependencies>
            <dependency>
              <groupId>org.junit.jupiter</groupId>
              <artifactId>junit-jupiter-api</artifactId>
              <version>5.9.0</version>
              <scope>test</scope>
            </dependency>
          </dependencies>

          <build>
            <plugins>
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                  <release>19</release>
                </configuration>
              </plugin>

              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M7</version>
              </plugin>
            </plugins>
          </build>
        </project>
      
Comme précédemment, créer un projet Maven, au niveau du premier écran, cocher create simple project puis passer à l'écran suivant en indiquant Next.
Pour ce TP, le groupId est fr.uge.seq , l'artefactId est seq et la version est 0.0.1-SNAPSHOT. Puis cliquer sur Finish.

Exercice 2 - Seq

La classe fr.uge.seq.Seq est paramétrée par le type des éléments qu'elle contient.

La classe Seq est munie des opérations initiales suivantes :
  • from qui prend une liste en paramètre (une java.util.List) et créé un objet Seq contenant les éléments de la liste (dans le même ordre);
  • get qui permet d'obtenir le n-ième élément d'un Seq (avec une comnplexité en O(1), bien sûr);
  • size qui indique le nombre d'éléments dans la séquence (en O(1), toujours).
    Note : comme la séquence est non mutable, le nombre d'éléments de la séquence ne change pas.

Exemple de création et d'utilisation d'un Seq
      var seq = Seq.from(List.of(78, 56, 34, 23));
      System.out.println(seq.size());  // 4
      System.out.println(seq.get(2));  // 34
     

Les tests unitaires JUnit 5 correspondant à l'implantation sont ici : SeqTest.java.

  1. Écrire le code de la classe Seq dans le package fr.uge.seq.

  2. Écrire une méthode d'affichage permettant d'afficher les valeurs d'un Seq séparées par des virgules (suivies d'un espace), l'ensemble des valeurs étant encadré par des chevrons ('<' et '>').
    Par exemple, avec le Seq créé précédemment
            System.out.println(seq);  // <78, 56, 34, 23>
          

  3. Écrire une méthode of permettant d'initialiser un Seq à partir de valeurs séparées par des virgules.
    Par exemple, on pourra créer le Seq précédent comme ceci
            var seq = Seq.of(78, 56, 34, 23);
          

    Note : si vous avez des warnings, vous avez un problème.
    Note 2 : si vous pensez un @SuppressWarnings, pensez plus fort !

  4. Écrire une méthode forEach qui prend en paramètre une fonction qui prend en paramètre chaque élément un par un et fait un effet de bord.
    Par exemple, on pourra afficher les éléments un par un de la façon suivante
            seq.forEach(element -> System.out.println(element));
          

  5. On souhaite écrire une méthode map qui prend en paramètre une fonction à appliquer à chaque élément d'un Seq pour créer un nouveau Seq. On souhaite avoir une implantation paresseuse, c'est-à-dire une implantation qui ne fait pas de calcul si ce n'est pas nécessaire. Par exemple, tant que personne n'accède à un élément du nouveau Seq, il n'est pas nécessaire d'appliquer la fonction. L'idée est de stoker les anciens éléments ainsi que la fonction et de l'appliquer seulement si c'est nécessaire.
    Bien sûr, cela va nous obliger à changer l'implantation déjà existante de Seq car maintenant tous les Seq vont stocker une liste d'éléments ainsi qu'une fonction de transformation (de mapping).
    Exemple d'utilisation
            var seq2 = seq.map(String::valueOf); // String.valueOf() est pas appelée
            System.out.println(seq2.get(0));     // "78", String.valueOf a été appelée 1 fois
                                                 // car on demande explicitement la valeur
          

    Avant de se lancer dans l'implantation de map, quelle doit être sa signature ?
    Quel doit être le type des éléments de la liste ? Et le type de la fonction stockée ?
    Faire les modifications correspondantes, puis changer le code des méthodes pour les prendre en compte. Enfin, écrire le code de map.
    Note : le code doit fonctionner si l'on appelle map deux fois successivement.

  6. Écrire une méthode findFirst qui renvoie le premier élément du Seq si celui-ci existe.

  7. Faire en sorte que l'on puisse utiliser la boucle for-each-in sur un Seq
    Par exemple,
            for(var value : seq) {
              System.out.println(value);
            }
          

  8. Enfin, on souhaite implanter la méthode stream() qui renvoie un Stream des éléments du Seq. Pour cela, on va commencer par implanter un Spliterator que l'on peut construire à partir du Spliterator déjà existant de la liste (que l'on obtient avec la méthode List.spliterator()).
    Puis en utilisant la méthode StreamSupport.stream, créer un Stream à partir de ce Spliterator.
    Écrire la méthode stream().

  9. (Optionnel) Si vous ne l'avez pas déjà fait, on souhaite que le Stream renvoyé par la méthode stream permette d’effectuer les calculs en parallèle sur les éléments du Seq.
    Modifier votre implantation (commentez l'ancienne) et vérifier que les tests marqués "Q9" passent.

Exercice 3 - Seq2 le retour (à la maison)

En fait, l'implantation à base de liste de l'exercice précédent n'est pas très efficace en mémoire. Il vaut mieux stocker les éléments dans un tableau car c'est de toute façon le stockage utilisé par la liste (pas complètement vrai si vous utilisez List.of(), on vous laisse trouver pourquoi).

Les tests unitaires JUnit 5 correspondant à l'implantation sont ici: Seq2Test.java.

  1. Ré-implanter toutes les méthodes publiques de Seq dans une classe Seq2 en utilisant un tableau d'éléments en interne.