:: Enseignements :: Master :: M1 :: 2023-2024 :: Java Avancé ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) |
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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 ?
© Université de Marne-la-Vallée