Le projet Epicrafter’s Journey avance dans la bonne direction. Les premiers retours sont positifs notamment grâce à la flexibilité du code écrit. La classe de base Bloc contient les éléments fondamentaux et les classes filles permettent d’avoir des blocs spécialisés !
Une nouvelle tâche vous est assignée, vous la découvrez avec empressement :
ID de la tâche : 7
Nom de la tâche : Créer un objet Kit
Description de la tâche :
Un kit est un ensemble de blocs qui peut être mis à disposition d’un joueur. L’objet doit permettre de gérer plusieurs blocs mais aussi plusieurs mots-clés qui correspondent à des idées de comment utiliser le kit. Par exemple :
Kit 1 :
- Blocs : [Mur],[Mur],[Mur],[Mur],[Porte]
- Mots-clés : Cabane, Muraille
La modélisation des éléments du programme est la suivante :
Écrivez un code générique
Après avoir examiné la tâche précédente, vous décomposez votre travail en plusieurs sous tâches :
Créer une classe pour gérer une liste de blocs.
Créer une classe pour gérer la liste des mots-clés.
Créer la classe Kit qui utilisera les précédentes.
Commençons par la première sous-tâche.
Comment faire créer une classe pour gérer une liste de blocs ?
Dans le chapitre sur les variables, vous avez appris que les tableaux permettent cela. Voici une proposition de code :
ListeBloc.java
public class ListeBloc {
private Bloc[] liste;
public ListeBloc(int quantite) {
this.liste = new Bloc[quantite]();
}
public void add(int indice, Bloc bloc) {
this.liste[indice] = bloc;
}
public Bloc get(int indice) {
return this.liste[indice];
}
}
Le constructeur permet d’initialiser le tableau et d’en choisir sa taille. Tandis que la méthode add nous permet d’ajouter un bloc dans le tableau et bien sûr la méthode get vous permet d’accéder à un bloc à partir de son indice dans le tableau.
Maintenant, comment faire pour gérer une liste de mots de clés ? Un mot-clé étant une chaîne de caractère, on va utiliser l’objet java.lang.String. Le code pourrait donc être le suivant :
ListeString.java
public class ListeMotCle {
private String[] liste;
public ListeMotCle(int quantite) {
this.liste = new String[quantite]();
}
public void add(int indice, int valeur) {
this.liste[indice] = valeur;
}
public String get(int indice) {
return this.liste[indice];
}
}
Avez-vous noté ? Très peu de choses changent entre ListeBloc et ListeMotCle. Principalement le type de données manipulées dans le tableau. C’est dommage, car le code se répète énormément ! Et on sait que la répétition freine la maintenabilité d’un code, nous ne voulons surtout pas ça !
Dans ces situations Java offre une solution : la généricité.
Notez le code qui suit :
Liste.java
public class Liste<T> {
private T[] liste;
public Liste(Class<T> classe, int quantite) {
this.liste = (T[]) Array.newInstance(classe, quantite);
}
public void add(int indice, int valeur) {
this.liste[indice] = valeur;
}
public T get(int indice) {
return this.liste[indice];
}
}
La notation <T> apposée après le nom de la classe permet d’indiquer un type générique. En fait, tant que l’objet n’est pas instancié, on ne sait pas encore quel type sera utilisé.
De ce fait, au lieu d’avoir 2 classes ListeBloc et ListeMotCle, nous pouvons en avoir une seule : Liste.
Du coup, dans la classe Kit cela donnera :
Kit.java
public class Kit {
private Liste<Bloc> blocs;
private Liste<String> motsclés;
public Kit() {
Liste<Bloc> blocs = new Liste<Bloc>(Bloc.class, 5);
blocs .add(0, new Mur(1,1,1,true));
blocs .add(1, new Mur(1,1,1,true));
blocs .add(2, new Mur(1,1,1,true));
blocs .add(3, new Mur(1,1,1,true));
blocs .add(3, new Porte(1,1,1,false));
Liste<String> motsclés= new Liste<String>(String.class, 2);
motsclés.add(0, “cabane”);
motsclés.add(1, “muraille”);
}
}
Le kit contient 4 blocs de type mur d’une longueur, largeur et hauteur de 1 et qui sont porteurs ainsi qu’une porte d’une longueur, largeur et hauteur de 1 et qui n’est pas verrouillée.
Mais revenons à notre objet Liste, c’est lors du typage de la variable et de l’instanciation de l’objet que nous spécifions le type !
C’est vraiment bien n’est-ce pas ? Notre code est maintenable car il y a une seule classe à corriger si besoin. Et il est évolutif car on peut avoir des instances de Liste avec tous les types complexes existants, sans aucune limite !
Ce code attire l’attention de Marc :
Je vois que tu as trouvé une bonne solution pour gérer une liste d'éléments, mais Java avait déjà prévu le nécessaire ! Tu devrais aller voir les collections.
Découvrez les collections
Les collections sont des objets disponibles dans le langage Java pour gérer des ensembles d'éléments, exactement ce que nous étions en train de faire !
Nous allons explorer trois catégories de collections. Chaque catégorie dépend d’une interface de base dont je vous donne une courte définition :
Interface | Définition | Exemple |
java.util.List | L’interface List est la base des collections permettant de manipuler des listes d’éléments autorisant des doublons. | Une liste de la quantité de blocs utilisés à chaque session de jeu. Il pourrait y avoir plusieurs fois la même quantité. |
java.util.Set | Les collections basées sur l’interface Set permettent de manipuler des listes d'éléments n’autorisant pas les doublons. | Une liste de mots-clés. Il n’y a pas de raison d’avoir deux fois le même mot-clé donc cette collection sera la plus adaptée. |
java.util.Map | La particularité des collections basées sur l’interface Map est qu’au lieu de manipuler un élément, c’est une association clé/valeur qui est exploitée. | Une quantité pour chaque bloc que l’on possède. La valeur est la quantité. |
Après cette brève présentation, arrêtons-nous sur chacune de ses collections.
Manipulez des listes de données
Dans la démonstration suivante, nous allons reprendre l’exemple mentionné dans le tableau précédent et implémenter une liste de la quantité de blocs utilisés à chaque session de jeu. Utilisons la classe ArrayList qui implémente l’interface List :
Que pouvons-nous retenir ?
La classe ArrayList s’instancie comme n’importe quelle classe.
On ajoute un élément à la liste avec la méthode add.
On supprime un élément de la liste avec la méthode remove.
La méthode statique valueOf de la classe java.lang.Integer permet de construire un objet complexe Integer à partir d’une valeur primitive.
Voici le code associé à cette démonstration :
Main.java
package ej;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> quantiteBlocsUtilises= new ArrayList<Integer>();
quantiteBlocsUtilises.add(1);
quantiteBlocsUtilises.add(3);
quantiteBlocsUtilises.add(1); // doublon autorisé
for(Integer quantite : quantiteBlocsUtilises) {
System.out.println(quantite);
}
quantiteBlocsUtilises.remove(Integer.valueOf(3));
}
}
Nous avons utilisé l’implémentation ArrayList mais il en existe d’autres :
Vector
Stack
LinkedList
CopyOnWriteArrayList
N’hésitez pas à aller découvrir leur spécificité
Passons à la deuxième catégorie de collections : les listes qui n’autorisent pas de doublons !
Manipulez des ensembles de données
Les classes qui implémentent l’interface Set sont plus restrictives que celle qui implémente l’interface List car elles n’autorisent pas les doublons. Cependant la manipulation est sensiblement similaire.
Prenons l’exemple de la liste de mots-clés où nous ne voulons pas avoir deux fois le même mot dans cette liste. Observez le code suivant :
Main.java
package ej;
import java.util.LinkedHashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> motsCles = new LinkedHashSet<String>();
motsCles .add("Cabane");
motsCles .add("Cabane");
motsCles .add("Muraille");
for(String motCle : motsCles) {
System.out.println(motCle);
}
}
}
Après exécution, la console affichera une seule fois le mot “Cabane” et non deux fois. Et oui pas de doublons !
Enfin regardez maintenant le code qui suit :
Main.java
package ej;
import java.util.LinkedHashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> motsCles = new LinkedHashSet<String>();
motsCles .add("Cabane");
for(String motCle : motsCles) {
System.out.println(motCle);
}
if(motsCles .contains("Cabane")) {
System.out.println("Le mot clé Cabane est présent !");
cours.remove(“Cabane”);
}
}
}
Cet exemple montre qu’on utilise les méthodes add et remove comme pour List. J’en ai profité pour vous faire découvrir la méthode contains qui permet de vérifier si un élément est présent dans la liste. Bien évidemment cette méthode existe aussi pour les implémentations de l’interface List.
Nous avons utilisé l’implémentation LinkedHastSet mais il en existe d’autres :
TreeSet
EnumSet
ConcurrentSkipListSet
CopyOnWriteArraySet
HashSet
N’hésitez pas à aller découvrir leur spécificité !
Manipulez des associations clés valeurs
Concluons ce chapitre avec les classes qui implémentent l’interface Map. Contrairement aux collections précédentes qui manipulent un seul élément, ici il s’agit de manipuler une association de clé valeur.
Dans la démonstration suivante, je reprend l’exemple mentionné précédemment où l’on veut définir une quantité pour chaque bloc que l’on possède :
A noter que :
J’ai défini aussi bien le type de la clé que celui de la valeur. Cela peut être deux types différents : Map<Bloc, Integer> par exemple.
J’ajoute une association clé valeur avec la méthode put.
J’accède à une valeur à partir de sa clé avec la méthode get.
Je peux parcourir la Map en extrayant les clés sous forme de Set puis en les parcourant.
Voici le code écrit :
Main.java
package ej;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Map<Bloc, Integer> quantiteBloc= new HashMap<Bloc, Integer>();
quantiteBloc.put(new Mur(1,1,1,true), 4);
quantiteBloc.put(new Porte(1,1,1,false), 4);
Set<Bloc> cles = quantiteBloc.keySet();
for(Bloc bloc : cles) {
Integer valeur = quantiteBloc.get(bloc);
System.out.println(bloc.getClass().getName() + " : " + valeur);
}
}
}
Je trouve très intéressant de noter que le type utilisé dans la définition de la HashMap est la classe Bloc tandis que lors de l’appel de la méthode put on y ajoute une instance de Mur puis de Porte, qui sont tous deux des classes filles de Bloc.
Et oui grâce à l’héritage, notre HashMap peut gérer n’importe quel type de Bloc indépendamment de sa spécialisation.
Encore une fois bien que nous avons utilisé l’implémentation HashMap, il en existe d’autres :
TreeMap
Hashtable
WeakHashMap
ConcurrentHashMap
LinkedHashMap
IdentityHashMap
EnumMap
N’hésitez pas à aller découvrir leur spécificité !
À vous de jouer
Contexte
Reprenons la tâche qui nous a été donnée en introduction :
ID de la tâche : 7
Nom de la tâche : Créer un objet Kit
Description de la tâche :
Un kit est un ensemble de blocs qui peut être mis à disposition d’un jour. L’objet doit permettre de gérer plusieurs blocs mais aussi plusieurs mots-clés qui correspondent à des idées de comment utiliser le kit. Par exemple :
Kit 1 :
- 5 Blocs : [Mur],[Mur],[Mur],[Mur],[Porte]
- Mots-clés : Cabane, Muraille
L’objectif sera de créer l’objet Kit en utilisant les collections mises à disposition en Java.
Voici les caractéristiques des 5 blocs :
Type | Longueur | Largeur | Hauteur | Spécificité |
Mur | 3 | 2 | 2 | porteur |
Mur | 3 | 2 | 2 | porteur |
Mur | 2 | 1 | 2 | non porteur |
Mur | 2 | 1 | 2 | non porteur |
Porte | 1 | 2 | 2 | verrouillée |
Consignes
Créer une classe nommée Kit.
Ajoutez un attribut dans la classe Kit pour gérer la liste des Blocs, choisissez entre List ou Set.
Ajoutez un attribut dans la classe Kit pour gérer la liste des mots-clés, choisissez entre List ou Set.
Écrivez un constructeur qui permet d’initialiser les listes.
Écrivez une méthode sans paramètre nommée afficherKit qui ne renvoie rien mais qui affiche dans la console le nombre de blocs du kit ainsi la liste des mots-clés.
La méthode principale sera la suivante :
package ej;
public class Main {
public static void main(String[] args) {
Kit kitDeDemarrage = new Kit();
kitDeDemarrage.afficherKit();
}
}
Le résultat de l’exécution devrait être :
En résumé
La généricité permet de définir le type manipulé à l’instanciation.
Les collections s'appuient sur la généricité pour manipuler des éléments indépendamment du type concerné.
Il existe plusieurs catégories de collections basées sur des interfaces comme List, Set et Map.
Dans le dernier chapitre de cette partie, nous aborderons un point important : la gestion des erreurs !