TP3 - Les collections⚓︎
Dans ce sujet de TP, le thème abordé est l'utilisation et la compréhension des classes du JDK permettant de modéliser des regroupements d'objets (des collections).
1. Présentation⚓︎
L'API (interface de programmation) Collection est une des API les plus utilisées en Java. Elle a été introduite dans la version 1.2 de Java, et entièrement réécrite dans la version 1.5 pour gérer les génériques. Cette API est présente dans le package java.util
.
Cette API a pour ojectif de permettre la gestion de collections d'objets, c'est-à-dire de regroupements d'objets. En effet, les tableaux ne permettent pas de répondre à tous les besoins de stockage et surtout ils manquent de fonctionnalités.
Cette API propose donc plusieurs interfaces (et plusieurs implémentations) afin de répondre aux différents besoins que nous pouvons avoir : notre collection peut être contenir des doublons ? doit-on pouvoir la trier ? ...
2. Interfaces principales⚓︎
Cette API contient principalement deux familles de regroupements d'objets : java.util.Collection
et java.util.Map
. Pour simplifier, une Collection
gère un regroupement des objets en question directement, alors qu'une Map
gère un regroupement de couple (clef, valeur). Les valeurs sont les objets en question, qui sont donc identifiés par une clef.
L'interface Collection
contient elle-même principalement deux sous-familles : java.util.List
et java.util.Set
.
API collection, collections et java.util.Collection
Attention, on utilise ici le même mot, "collection", pour trois choses différentes :
- Le nom de cette API : "Collection".
- Des regroupements d'objets : ces sont des "collections".
- Une partie de ce package (la branche de gauche dans schéma ci-dessous) : l'interface
java.util.Collection
.
Nous parlerons donc ici des interfaces List
, Set
et Map
.
Voici un diagramme simplifié des principales interfaces proposées par cette API :
graph BT;
D[java.util.List] -.-> B[java.util.Collection];
F[java.util.SortedSet] -.-> E[java.util.Set];
E[java.util.Set] -.-> B[java.util.Collection];
G[java.util.SortedMap] -.-> C[java.util.Map];
B[java.util.Collection] -.-> A([API Collection]);
C[java.util.Map] -.-> A([API Collection]);
Diagramme de java.util.Collection
Voici un diagramme plus détaillé des interfaces et classes proposées par cette API (dans la branche de gauche du diagramme ci-dessus : java.util.Collection
).
3. Utilisation de ces interfaces⚓︎
Chacune de ces interfaces propose plusieurs classes les implémentant, mais voici les plus "classiques", avec une explication sur quand les utiliser :
Interface | Implémentation "classique" | Éléments ordonnés | Accès direct à un élément | Clé / valeur | Doublons autorisés ? | Correspondance en Python |
---|---|---|---|---|---|---|
List |
java.util.ArrayList |
Oui | Oui | Non | Oui | list : [1, 2, 3] |
Set |
java.util.HashSet |
Non | Non | Non | Non | set : {1, 2, 3} |
SortedSet |
java.util.TreeSet |
Oui | Non | Non | Non | N/A |
Map |
java.util.HashMap |
Non | Oui | Oui | Non 1 | dict : {1: "un", 2: "deux"} |
SortedMap |
java.util.TreeMap |
Oui | Oui | Oui | Non 2 | N/A |
- Éléments ordonnés : cela signifie que les éléments sont par défaut dans l'ordre dans lequel ils ont été ajoutés. La collection est de plus triable (cf. ci-dessous
Comparable
etComparator
). - Accès direct à un élément : cela signifie qu'on peut accéder directement à un élément, sans avoir à parcourir toute la collection.
- Clé / valeur : cela signifie que chaque élément de la collection est un couple (clé, valeur). La clé peut être n'importe quel objet java (
java.lang.Object
), mais il est préférable (et fortement conseillé) que les méthodesequals()
ethashCode()
soient surchargées. La valeur peut également être n'importe quoi. - Doublons autorisés ? : est-il possible d'avoir deux fois le même objet dans la collection ?
4. Contenu supplémentaire⚓︎
Cette API fournit également :
- deux interfaces pour le parcours de certaines collections :
java.util.Iterator
etjava.util.ListIterator
. - deux interfaces pour permettre le tri de certaines collections :
java.lang.Comparable
etjava.util.Comparator
(exemple ici) :Comparable
permet d'indiquer comment des objets doivent être triés.- On souhaite parfois pouvoir trier une collection d'objets sur différentes propriétés. On souhaite par exemple à un moment trier une liste de personne par rapport à leur nom, puis ensuite par rapport à leur prénom, puis par rapport à leur âge, puis par rapport à leur profession ... On utilise alors autant de
Comparator
que souhaité.
- des classes utilitaires :
java.util.Arrays
,java.util.Collections
.
5. Exercices⚓︎
Avant de commencer les exercices, dans Eclipse, créer un projet Java nommé TPCollections
.
Exercice 1 - La structure Notes
-
Écrire une structure de données permettant de représenter un ensemble de notes, d'y ajouter des notes et de calculer la moyenne de celles-ci. Vous utiliserez la classe
ArrayList
pour représenter cet ensemble.Voici le diagramme de cette classe
fr.univtours.polytech.tpcollections.Notes
:classDiagram Notes class Notes{ -notesList: List~Double~ +Notes() +getNotesList() List~Double~ +addNote(note: Double) +computeMean() Double }
Note
Depuis la version 1.5 de Java, on utilise des génériques. Cela signifie que lorsqu'on définit une liste par exemple, on indique quel est le type d'objet qu'elle contient. En indiquant
private List<Double> notesList
, on précise que cette liste ne contient que desDouble
, ou des objets dont la classe hérite deDouble
.Note
On utilisera donc la classe
java.util.ArrayList
comme implémentation de l'interfacejava.util.List
.Cela signifie qu'on peut écrire des choses comme
List<Double> maList = new ArrayList<Double>();
.- À gauche du
=
, on définit l'objet : on indique que c'est uneList
, c'est-à-dire qu'on indique les fonctionnalités que notre objet doit avoir. - À droite du
=
, on précise l'implémentation qu'on a choisit : on indique que c'est uneArrayList
, c'est-à-dire qu'on précise comment les fonctionnalités que notre objet doit avoir sont implémentées.
- À gauche du
-
Cette classe possède un constructeur public sans argument. Son rôle est d'initialiser chacuns des attributs de la classe. Dans le cas présent, ce constructeur ressemblera à cela :
☕ Code Javapublic Notes() { this.notesList = new ArrayList<Double>(); }
-
Écrire une classe
fr.univtours.polytech.tpcollections.TestNotes
, contenant une méthodemain
permettant de tester cette classeNotes
.Pour cela, initialiser un objet
Notes
, lui ajouter les notes 10, 12 et 20, et vérifier que l'exécution de la méthodecomputeMean
renvoie bien 14 (ou plutôt 14.0).Les
Double
En Java, en ajoutant
D
après un nombre à virgule, on obtient directement un Double. Il est donc possible d'écrire :☕ Code Javanotes.addNote(18D);//(1)!
- On ajoute la note 18 dans l'objet
Notes
.
- On ajoute la note 18 dans l'objet
Exercice 2 - La structure Étudiant
-
Écrire une structure de données (une classe) permettant de représenter une matière, chaque matière aura un nom et un coefficient.
Voici le diagramme de cette classe
fr.univtours.polytech.tpcollections.Subject
:classDiagram Subject class Subject{ -name: String -coefficient: Double +Subject(name: String, coefficient: Double) +getName() String +setName(name: String) +getCoefficient() Double +setCoefficient(coefficient: Double) }
-
Compléter la classe
fr.univtours.polytech.tpcollections.TestNotes
pour tester cette nouvelle classe.Pour cela, on créera deux matières :
- Informatique, avec un coefficient de 1,5.
- Mathématiques, avec un coefficient de 2,5.
-
Écrire une structure de données (une classe) permettant de représenter un étudiant. Chaque étudiant possèdera un nom et une liste de notes par matière.
Il sera en outre possible de calculer sa moyenne pondérée (moyenne en prenant en compte le coefficient de chaque matière).
Voici le diagramme de cette classe
fr.univtours.polytech.tpcollections.Student
:classDiagram Student class Student{ -name: String -notesList: Map~Subject, Notes~ +Student(name: String) +getName() String +setName(name: String) +getnotesList() Map +computeMean() Double }
java.util.HashMap
comme implémentation de l'interfacejava.util.Map
.À nouveau, le constructeur public à uniquement comme rôle d'initiliser cette
Map
:☕ Code Javapublic Student(String name) { this.name = name; this.notesList = new HashMap<Subject, Notes>(); }
Les
Map
en JavaPour rappel, pour parcourir un dictionnaire en Python, on fait
🐍 Script Pythonfor clef in dictionnaire: dictionnaire[clef] # permet d'obtenir la valeur associée à clef.
En Java, l'idée est là même :
- La méthode
Map.keySet()
permet d'obtenir la liste (en fait l'ensemble) des clefs. - La méthode
Map.get(Object key)
permet d'obtenir la valeur associée à la clef.
Cela donne donc (dans notre exemple, la clef est un objet
Subject
et la valeur associée un objetNotes
) :☕ Code Javafor (Subject matiere : this.notesList.keySet()) { this.notesList.get(matiere); // permet d'obtenir la valeur associée à la clef. }
- La méthode
-
Compléter la classe
fr.univtours.polytech.tpcollections.TestNotes
pour tester cette nouvelle classe. On créera une étudiante : "Alice".Alice aura :
- Les trois notes créées dans l'exercice 1 (10, 12 et 20) en informatique;
- 18 et 20 en mathématiques.
Vérifier qu'Alice a bien 17,125 de moyenne.
Comment ajouter des notes à Alice en maths ?
... ou de manière générale, comment ajouter des couples (clef, valeurs) dans une
Map
?De la même manière que
Map.get(Object key)
permet d'accéder à la valeur associée à la clefkey
, la méthodeMap.put(Object key, Object value)
permet d'ajouter un nouveau couple dans laMap
.On souhaite par exemple indiquer qu'Alice a eu 18 et 20 en maths.
☕ Code Java// Création de la matière. Subject maths = new Subject("Mathématiques", 2.5D); // Création de l'étudiante. Student alice = new Student("Alice"); // Création de ses notes : Notes aliceEnMaths = new Notes(); aliceEnMaths.addNote(18D); aliceEnMaths.addNote(20D); // On récupère l'objet contenant toute les notes d'Alice : alice.getNotesList() ... // ... auquel on ajoute le couple choisi : .put(...) alice.getNotesList().put(maths, aliceEnMaths);
Exercice 3 - La structure Promotion
-
Écrire une structure de données permettant de représenter une promotion qui est une liste d'étudiant. Une promotion étudie dans une année précise avec une liste de matières.
Il doit être possible d'obtenir :
- la moyenne générale de la promotion,
- la moyenne par matière,
- le classement de la promotion,
- les meilleures et moins bonne notes dans chaque matière.
Voici donc le diagramme de cette classe
fr.univtours.polytech.tpcollections.YearGroup
:classDiagram YearGroup class YearGroup{ -year: Integer -students: List~Student~ -subjects: List~Subject~ +YearGroup() +getYear() Integer +setYear(year: Integer) +getStudents() List~Student~ +setStudents(students: List~Student~>) +getSubjects() List~Subject~ +setSubjects(subjects: List~Subject~) +computeGroupMean() Double +computeSubjectMean(subject: Subject) Double +getGroupRanking() List~Student~ +getBestNote(subject: Subject) Double +getWorseNote(subject: Subject) Double }
Trier des collections
Vous trouverez un objet sur l'utilisation de l'interface
java.util.Comparator
ici : https://stackoverflow.com/questions/2839137/how-to-use-comparator-in-java-to-sort.On pensera à utiliser des "expressions lambda" (cf. dernière partie de la réponse dans le post ci-dessus, après le EDIT).
-
Créer la classe
fr.univtours.polytech.tpcollections.TestNotesFinal
, afin de vérifier le bon fonctionnement de notre modèle. Il suffit de copier le code suivant :☕ Code Javapublic static void main(String[] args) { // Création de deux matières : Subject info = new Subject("Informatique", 1D); Subject maths = new Subject("Mathématiques", 2D); // Création de 3 étudiants : Student alice = new Student("Alice"); Student bob = new Student("Bob"); Student charlie = new Student("Charlie"); List<Student> students = new ArrayList<Student>(); students.add(alice); students.add(bob); students.add(charlie); // Création de la promotion : YearGroup yearGroup = new YearGroup(); yearGroup.setYear(2022); yearGroup.setStudents(students); // Alice a 14 en info et 20 en maths. Notes aliceEnInfo = new Notes(); aliceEnInfo.addNote(14D); alice.getNotesList().put(info, aliceEnInfo); Notes aliceEnMaths = new Notes(); aliceEnMaths.addNote(20D); alice.getNotesList().put(maths, aliceEnMaths); // Bob a 16 en info et 10 en maths. Notes bobEnInfo = new Notes(); bobEnInfo.addNote(16D); bob.getNotesList().put(info, bobEnInfo); Notes bobEnMaths = new Notes(); bobEnMaths.addNote(10D); bob.getNotesList().put(maths, bobEnMaths); // Charlie a 15 en info et 15 en maths. Notes charlieEnInfo = new Notes(); charlieEnInfo.addNote(15D); charlie.getNotesList().put(info, charlieEnInfo); Notes charlieEnMaths = new Notes(); charlieEnMaths.addNote(15D); charlie.getNotesList().put(maths, charlieEnMaths); System.out.println("Classement :"); System.out.println(yearGroup.getGroupRanking()); System.out.println("Moyenne générale :"); System.out.println(yearGroup.computeGroupMean()); System.out.println("Moyenne en maths :"); System.out.println(yearGroup.computeSubjectMean(maths)); System.out.println("Meilleure note en maths :"); System.out.println(yearGroup.getBestNote(maths)); System.out.println("Moins bonne note en maths :"); System.out.println(yearGroup.getWorseNote(maths)); }
-
Dans la classe
Student
, surcharger la méthodetoString()
pour qu'elle affiche le nom de l'étudiant. -
Vérifier que l'exécution de cette classe renvoie le résultat suivant :
ConsoleClassement : [Alice, Charlie, Bob] Moyenne générale : 15.0 Moyenne en maths : 15.0 Meilleure note en maths : 20.0 Moins bonne note en maths : 10.0