:: Enseignements :: ESIPE :: E4INFO :: 2026-2027 :: Java Avancé ::
![[LOGO]](http://monge.univ-eiffel.fr/ens/resources/mlv.png) |
Slices of bread
|
Vue d'un tableau, classe interne, classe anonyme et encore un peu de lambdas
Le but de ce TP est d'écrire un slice (une vue partielle d'un tableau)
pour comprendre les notions de vue, 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 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.14.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.16.0</version>
<configuration>
<release>27</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.0</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 (
array slicing)
est une structure de données classique 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.
Une implantation d'une structure de données qui ne stocke pas les éléments
est appelée une
vue. C'est une structure de données plus efficace qu'une
ArrayList,
car on ne copie pas les éléments du tableau.
Attention, contrairement aux autres structures de données,
une vue
ne respecte pas le principe d'encapsulation, car les éléments n'appartiennent pas à la vue,
ils ne sont pas de la responsabilité de la vue.
En Java, les slices ne sont pas appelés des slices, mais sont accessibles à travers l'interface
List
en utilisant les méthodes
Arrays.asList(array) et
List.subList(from, to)
et
List.reversed(),
c'est ce que nous souhaitons réimplanter (donc on ne va pas utiliser ces méthodes !).
-
Pour ce TP, nous allons séparer l'interface Slice de son implantation SliceImpl.
Un Slice correspond à l'interface suivante :
public interface Slice<E> { }
L'interface Slice est paramétrée par le type des éléments du tableau
sur lequel le Slice est créé. Les éléments peuvent être null.
Voici un exemple d'utilisation d'un Slice
String[] array = new String[] { "foo", "bar", "baz", "whizz" };
Slice<String> slice = Slice.of(array, 1, 3); // creation
System.out.println(slice); // [bar, baz]
La méthode of permet de créer un slice sur un tableau avec les paramètres from et to,
qui sont respectivement l'index du premier élément du slice et l'index après le dernier élément.
Autrement dit, un slice correspond aux éléments dans l'intervalle [from, to[
(from inclus et to exclu).
Rappeler pourquoi on doit déclarer encore une fois E en début de la méthode of
static <E> Slice<E> of(E[] elements, int from, int to) {
...
}
Écrire l'interface Slice et sa méthode of sachant que l'on va déclarer SliceImpl,
l'implantation de l'interface Slice, en tant que classe interne de l'interface.
On souhaite pouvoir afficher le contenu d'un slice avec la même syntaxe que pour une List.
Sachant qu'il existe une méthode java.util.Arrays.stream(array, start, end),
implanter la méthode d'affichage en utilisant un Stream.
Vérifier que les tests marqués "Q1" passent.
Note : pour les préconditions, il existe les méthodes java.util.Objects.checkIndex() et
Objects.checkFromToIndex().
-
var array = new String[] { "foo", "bar", "baz", "whizz" };
var slice = Slice.of(array, 1, 3);
System.out.println(slice); // [bar, baz]
On souhaite maintenant pouvoir accéder à la taille du Slice
ainsi qu'au nième élément du slice.
Pour cela, on va ajouter les méthodes size et get(index).
Avec notre exemple :
String[] array = new String[] { "foo", "bar", "baz", "whizz" };
Slice<String> slice = Slice.of(array, 1, 3);
System.out.println(slice.size()); // 2
System.out.println(slice.get(0)); // bar
System.out.println(slice.get(1)); // baz
Implanter ces méthodes.
Vérifier que les tests marqués "Q2" passent.
-
On souhaite ajouter à l'interface Slice une méthode replaceAll qui permet
de remplacer chaque élément. La fonction passée en paramètre indique comment transformer
un élément en celui qui le remplace.
Par exemple, pour supprimer les espaces à gauche et à droite des chaînes de caractères
(avec la méthode String.trim()), on peut écrire
var array = new String[]{ "foo", " bar", " baz ", "whizz " };
var slice = Slice.of(array, 1, 4);
slice.replaceAll(String::trim);
System.out.println(slice.get(0)); // bar
System.out.println(slice.get(1)); // baz
System.out.println(slice.get(2)); // whizz
System.out.println(array[1]); // bar
On peut remarquer que la méthode replaceAll ne renvoie pas un nouveau Slice,
mais le modifie. Donc la méthode replaceAll change les valeurs dans les cases du Slice.
Et comme c'est une vue, cela signifie que l'on change les valeurs dans les cases du tableau sous-jacent.
Quelle est l'interface fonctionnelle que l'on doit utiliser en paramètre de replaceAll ?
Ajouter la méthode replaceAll à l'interface Slice et modifier votre implantation
en conséquence.
Vérifier que les tests marqués "Q3" passent.
-
On souhaite maintenant implanter une méthode reversed qui renvoie un nouveau slice
qui permet de voir les éléments en sens inverse (sans copier les éléments).
Voici un exemple d'utilisation
var array = new String[]{ "foo", "bar", "baz", "whizz" };
var slice = Slice.of(array, 1, 4);
var reversed = slice.reversed();
System.out.println(reversed.size()); // 3
System.out.println(reversed.get(0)); // whizz
System.out.println(reversed.get(1)); // baz
System.out.println(reversed.get(2)); // bar
On peut remarquer que pour renverser un slice, il n'est pas nécessaire de connaître l'implantation
si on a accès aux méthodes size et get(index).
On peut donc implanter la méthode reversed sous forme de méthode par défaut directement
dans l'interface.
Implanter la méthode reversed sachant qu'en termes d'implantation, nous allons utiliser
une classe anonyme.
Astuce: pour replaceAll(), vu que l'on remplace tous les éléments,
donc l'ordre de parcours n'a pas d'importance !
Vérifier que les tests marqués "Q4" passent.
-
On souhaite ajouter une optimisation à notre implantation de reversed,
si on reversed() un slice déjà reversed() on obtient le slice original.
var array = new String[]{ "foo", "bar", "baz", "whizz" };
var slice = Slice.of(array, 1, 4);
var slice2 = slice.reversed().reversed(); // même objet que slice
Vérifier que les tests marqués "Q5" passent.
-
On souhaite écrire une méthode subSlice(from, to) sur l'interface Slice
qui renvoie un slice qui est une sous-partie du slice sur lequel on appelle la méthode subSlice.
Par exemple
var array = new String[]{ "foo", "bar", "baz", "whizz" };
var slice = Slice.of(array, 1, 4);
var subSlice = slice.subSlice(1, 2);
System.out.println(subSlice.size()); // 1
System.out.println(subSlice.get(0)); // "baz"
Dans l'exemple ci-dessous, on crée un slice sur le tableau avec l'intervalle [1, 4[, puis on crée un second
slice sur le premier slice avec l'intervalle [1, 2[. C'est équivalent à créer un slice sur [2, 3[
sur le tableau.
De plus, il faudrait aussi implanter la méthode subSlice(from, to) quand le Slice
est reversed(), mais pour l'instant, on va tricher, l'implantation de cette méthode
va lever l'exception UnsupportedOperationException avec un beau TODO
pour dire qu'on l'implantera plus tard.
@Override
public Slice<E> subSlice(int from, int to) {
throw new UnsupportedOperationException("TODO");
}
Implanter les méthodes subSlice.
Vérifier que les tests marqués "Q6" passent.
-
On souhaite maintenant implanter la méthode subSlice() dans reversed().
Par exemple :
var array = new String[]{ "foo", "bar", "baz", "whizz" };
var slice = Slice.of(array, 1, 4);
var reversed = slice.reversed();
var subSlice = reversed.subSlice(1, 3);
System.out.println(subSlice.get(0)); // baz
System.out.println(subSlice.get(1)); // bar
Implanter la méthode subSlice(from, to) dans la classe anonyme dans la méthode reversed().
Vérifier que les tests marqués "Q7" passent.
Note : vous avez le droit de prendre un papier pour comprendre comment calculer les index correctement.
-
[Revision] Enfin, pour aller plus loin, on souhaiterait pouvoir créer un slice juste en passant
des éléments séparés par des virgules.
Par exemple
var slice = Slice.of("foo", "bar", "baz", "whizz");
System.out.println(slice.size()); // 4
Implanter cette surcharge de la méthode of sachant que les tests doivent compiler sans warnings
(et changer les tests n'est pas la solution !).
Vérifier que les tests marqués "Q8" passent.
© Université de Marne-la-Vallée