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

Slices of bread


Vue d'un tableau, classe interne, inner class et classe anonyme
Le but de ce TP est d'écrire 4 versions différentes du concept de slice (une vue partielle d'un tableau) pour comprendre les notions de classe interne et de classe anonyme en Java.

Exercice 1 - Maven

Comme pour le TP précédent, nous allons utiliser Maven avec une configuration (le pom.xml) très similaire, ici, nous n'avons pas besoin du pattern matching, donc pas besoin d'activer les preview features.
<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.slice</groupId>
    <artifactId>slice</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.10.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.2</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.slice , l'artefactId est slice et la version est 0.0.1-SNAPSHOT. Puis cliquer sur Finish.

Exercice 2 - The Slice and The furious

Un slice est une structure de données qui permet de "virtuellement" découper un tableau en gardant des indices de début et de fin (from et to) ainsi qu'un pointeur sur le tableau. Cela évite de recopier tous les éléments du tableau, c'est donc beaucoup plus efficace.
Le concept d'array slicing est un concept très classique dans les langages de programmation, même si chaque langage vient souvent avec une implantation différente.

Les tests JUnit 5 de cet exercice sont SliceTest.java.

  1. On va dans un premier temps créer une interface Slice avec une méthode array qui permet de créer un slice à partir d'un tableau en Java.
           String[] array = new String[] { "foo", "bar" };
           Slice<String> slice = Slice.array(array);
         
    L'interface Slice est paramétrée par le type des éléments du tableau et permet que les éléments soient null.
    L'interface Slice possède deux méthodes d'instance, size qui renvoie le nombre d'éléments et get(index) qui renvoie le index-ième (à partir de zéro).
    En termes d'implantation, on va créer une classe interne à l'interface Slice nommée ArraySlice implantant l'interface Slice. Cette implantation doit être la seule possible. L'implantation ne doit pas recopier les valeurs du tableau donc un changement d'une des cases du tableau doit aussi être visible si on utilise la méthode get(index).
    Écrire l'interface Slice puis implanter la classe SliceArray et les méthodes array, size et get(index).
    Vérifier que les tests JUnit marqués "Q1" passent.

  2. On souhaite que l'affichage d'un slice affiche les valeurs séparées par des virgules avec un '[' et un ']' comme préfixe et suffixe.
    Par exemple,
          var array = new String[] { "foo", "bar" };
          var slice = Slice.array(array);
          System.out.println(slice);   // [foo, bar]
        
    En terme d'implantation, penser à utiliser un Stream avec le bon Collector !
    Vérifier que les tests JUnit marqués "Q2" passent.

  3. On souhaite ajouter une surcharge à la méthode array qui, en plus de prendre le tableau en paramètre, prend deux indices from et to et montre les éléments du tableau entre from inclus et to exclus.
    Par exemple
           String[] array = new String[] { "foo", "bar", "baz", "whizz" };
           Slice<String> slice = Slice.array(array, 1, 3);
         

    En terme d'implantation, dans Slice, on va créer une autre classe interne nommée SubArraySlice implantant l'interface Slice.
    Vérifier que les tests JUnit marqués "Q3" passent.
    Note : pour l'affichage, il existe une méthode Arrays.stream(array, from, to) dans la classe java.util.Arrays.

  4. On souhaite enfin ajouter une méthode subSlice(from, to) à l'interface Slice qui renvoie un sous-slice restreint aux valeurs entre from inclus et to exclu.
    Par exemple,
            String[] array = new String[] { "foo", "bar", "baz", "whizz" };
            Slice<String> slice = Slice.array(array);
            Slice<String> slice2 = slice.subSlice(1, 3);
          

    Bien sûr, cela veut dire implanter la méthode subSlice(from, to) dans les classes ArraySlice et SubArraySlice.
    Vérifier que les tests JUnit marqués "Q4" passent.
Nous avons fini l'implantation de l'interface Slice est utilisant des classes internes, nous allons dans l'exercice suivant utiliser une inner class au lieu d'une classe interne.

Exercice 3 - 2 Slice 2 Furious

Le but de cet exercice est d'implanter l'interface Slice2 qui possède les mêmes méthodes que l'interface Slice mais on souhaite de la classe SubArraySlice soit une inner class de la classe ArraySlice.

Les tests JUnit 5 de cet exercice sont Slice2Test.java.

  1. Recopier l'interface Slice de l'exercice précédent dans une interface Slice2. Vous pouvez faire un copier-coller de Slice dans même package, votre IDE devrait vous proposer de renommer la copie. Puis supprimer la classe interne SubArraySlice ainsi que la méthode array(array, from, to) car nous allons les réimplanter et commenter la méthode subSlice(from, to) de l'interface, car nous allons la ré-implanter aussi, mais plus tard.
    Vérifier que les tests JUnit marqués "Q1" et "Q2" passent.

  2. Déclarer une classe SubArraySlice à l'intérieur de la classe ArraySlice comme une inner class donc pas comme une classe statique et implanter cette classe et la méthode array(array, from, to).
    Vérifier que les tests JUnit marqués "Q3" passent.

  3. Dé-commenter la méthode subSlice(from, to) de l'interface et fournissez une implantation de cette méthode dans les classes ArraySlice et SubArraySlice.
    On peut noter qu'il est désormais possible de simplifier le code de array(array, from, to).
    Vérifier que les tests JUnit marqués "Q4" passent.

  4. Dans quel cas va-t-on utiliser une inner class plutôt qu'une classe interne ?
Nous avons fini l'implantation de l'interface Slice est utilisant une classe interne et une inner class, nous allons maintenant ré-écrire l'interface Slice une troisième fois en utilisant deux classes anonymes.

Exercice 4 - The Slice and The Furious: Tokyo Drift

Le but de cet exercice est d'implanter l'interface Slice3 qui possède les mêmes méthodes que l'interface Slice mais on va transformer les classes ArraySlice et SubArraySlice en classes anonymes.

Les tests JUnit 5 de cet exercice sont Slice3Test.java.

  1. Recopier l'interface Slice du premier exercice dans une interface Slice3. Supprimer la classe interne SubArraySlice ainsi que la méthode array(array, from, to) car nous allons les réimplanter et commenter la méthode subSlice(from, to) de l'interface, car nous allons la réimplanter plus tard.
    Puis déplacer la classe ArraySlice à l'intérieur de la méthode array(array) et transformer celle-ci en classe anonyme.
    Vérifier que les tests JUnit marqués "Q1" et "Q2" passent.

  2. On va maintenant chercher à implanter la méthode subSlice(from, to) directement dans l'interface Slice3. Ainsi, l'implantation sera partagée.
    Écrire la méthode subSlice(from, to) en utilisant là encore une classe anonyme.
    Comme l'implantation est dans l'interface, on n'a pas accès au tableau qui n'existe que dans l'implantation donnée dans la méthode array(array)... mais ce n'est pas grave, car on peut utiliser les méthodes de l'interface.
    Puis fournissez une implantation à la méthode array(array, from, to).
    Vérifier que les tests JUnit marqués "Q3" et "Q4" passent.

  3. Dans quel cas va-t-on utiliser une classe anonyme plutôt qu'une classe interne ?

Exercice 5 - Slice & Furious (optionnel)

Le but de cet exercice est d'implanter l'interface Slice4 qui possède les mêmes méthodes que l'interface Slice mais au lieu d'avoir deux implantations, on va simplifier les choses en ayant une seule implantation.
De plus, au lieu d'utiliser une classe interne, une inner class ou une classe anonyme, on va utiliser une feature assez méconnue de Java : on peut avoir plusieurs classes/interfaces les unes derrières les autres dans un même fichier en Java, pourvu qu'une seule classe/interface soit publique.

Les tests JUnit 5 de cet exercice sont Slice4Test.java.

  1. Déclarer l'interface Slice4 avec les méthodes size, get(index) et subSlice(from, to) abstraites. De plus, la méthode array(array) peut déléguer son implantation à la méthode array(array, from, to).
    Pour l'instant, commenter la méthode subSlice(from, to) que l'on implantera plus tard.
    À la suite du fichier, déclarer une classe non publique SliceImpl implantant l'interface Slice4 et implanter la méthode array(array, from, to).
    Vérifier que les tests JUnit marqués "Q1", "Q2" et "Q3" passent.

  2. Dé-commenter la méthode subSlice(from, to) et fournissez une implantation de cette méthode.
    Vérifier que les tests JUnit marqués "Q4" passent.

  3. On peut remarquer qu'en programmation objet il y a une toujours une tension entre avoir une seule classe et donc avoir des champs qui ne servent pas vraiment pour certaines instances et avoir plusieurs classes ayant des codes très similaires, mais avec un nombre de champs différents.
    L'orthodoxie de la POO voudrait que l'on ait juste le nombre de champs qu'il faut, en pratique, on a tendance à ne pas créer trop de classes, car plus on a de code plus c'est difficile de le faire évoluer.
    À votre avis, pour cet exemple, est-il préférable d'avoir deux classes une pour les tableaux et une pour les tableaux avec des bornes ou une seule classe gérant les deux cas ?