Bonjour ! Dans la leçon précédente, nous avons exploré HashMap et sa manière efficace de stocker des données en paires clé-valeur. Le concept clé à retenir était l'unicité des clés, garantie par le mécanisme de hachage via les méthodes hashCode() et equals(). Aujourd'hui, nous allons voir comment ce même principe est utilisé dans une autre structure de données fondamentale : le HashSet.
L'objectif de cette leçon est de vous apprendre à utiliser HashSet pour créer des collections qui garantissent l'unicité de leurs éléments. C'est une tâche extrêmement courante en programmation, que ce soit pour dédoublonner une liste de données, suivre les éléments déjà traités, ou simplement s'assurer qu'une collection ne contient pas de répétitions. Vous verrez que votre compréhension de HashMap rendra l'apprentissage de HashSet très intuitif.
1. L'interface Set : le contrat de l'unicité
Dans le Framework des Collections Java, l'interface Set définit le contrat d'une collection qui ne peut pas contenir d'éléments en double. C'est la représentation informatique d'un ensemble mathématique.
Observez sa position dans la hiérarchie des collections :

La principale différence avec List, que nous avons vue avec ArrayList, est simple :
- Une
Listpeut contenir des doublons et maintient un ordre (les éléments sont accessibles par un index). - Un
Setne peut pas contenir de doublons et, dans le cas deHashSet, ne garantit aucun ordre spécifique.
Pour bien visualiser cette différence fondamentale, la vidéo suivante est très parlante.
Apprendre Java #10 Collections (List / Set)
Cette vidéo de la chaîne JirAWS, intitulée "Apprendre Java #10 Collections (List / Set)", compare directement le comportement d'une List et d'un Set lorsqu'on tente d'y ajouter un doublon.
Regardez la section List vs Set. Elle démontre clairement comment un Set refuse silencieusement d'ajouter un élément qui est déjà présent, garantissant ainsi l'unicité.
2. HashSet : l'unicité grâce à HashMap
HashSet est l'implémentation la plus courante et la plus performante de l'interface Set. Son secret ? Elle est entièrement construite sur une HashMap !
Voici comment cela fonctionne en interne :
- Quand vous créez un
HashSet<String>, Java crée en coulisses uneHashMap<String, Object>. - Lorsque vous appelez
monSet.add("Java"),HashSetexécute en réalitémaMapInterne.put("Java", PRESENT), oùPRESENTest un objet factice constant partagé par toutes les entrées.
Puisque les clés d'une HashMap doivent être uniques, les éléments d'un HashSet le sont aussi par construction. Cette astuce de conception est élégante et efficace. Cela signifie également que toutes les règles que nous avons vues pour les clés de HashMap s'appliquent aux éléments d'un HashSet : leur bon fonctionnement dépend de l'implémentation correcte des méthodes hashCode() et equals().
L'image suivante résume parfaitement le fonctionnement interne de HashSet.

3. Utilisation pratique de HashSet
Manipuler un HashSet est très simple. Les méthodes principales sont intuitives.
Java - HashSet et TreeSet : Guide complet des ensembles
Cet article de Rachid Saad Academy présente un premier exemple de code très clair qui illustre les opérations de base sur un HashSet.
Analysez l'exemple de code dans la section "1. Introduction aux ensembles (Set) en Java", sous le titre "PREMIER CONTACT AVEC LES ENSEMBLES". Portez une attention particulière au comportement de la méthode add() lorsqu'on tente d'insérer un doublon et à l'ordre d'affichage qui n'est pas garanti.
Voici un résumé des méthodes essentielles avec un exemple :
boolean add(E element): Ajoute l'élément. Retournetruesi l'élément n'était pas déjà présent (la collection a été modifiée), etfalsesinon.boolean remove(Object element): Supprime l'élément s'il est présent.boolean contains(Object element): Vérifie si l'élément est présent. C'est une opération très rapide ().int size(): Retourne le nombre d'éléments.void clear(): Vide l'ensemble.
import java.util.HashSet;
import java.util.Set;
public class DemoHashSet {
public static void main(String[] args) {
// Bonne pratique : déclarer avec l'interface Set
Set<String> langages = new HashSet<>();
// 1. Ajout d'éléments
System.out.println("Ajout de 'Java': " + langages.add("Java"));
System.out.println("Ajout de 'Python': " + langages.add("Python"));
System.out.println("Ajout de 'JavaScript': " + langages.add("JavaScript"));
System.out.println("\nEnsemble actuel : " + langages); // L'ordre n'est pas garanti
// 2. Tentative d'ajout d'un doublon
System.out.println("Ajout de 'Java' (doublon): " + langages.add("Java"));
System.out.println("Ensemble après ajout du doublon : " + langages);
System.out.println("Taille : " + langages.size()); // La taille reste 3
// 3. Vérification de présence
System.out.println("\nL'ensemble contient 'Python' ? " + langages.contains("Python"));
System.out.println("L'ensemble contient 'C++' ? " + langages.contains("C++"));
// 4. Suppression
langages.remove("JavaScript");
System.out.println("\nEnsemble après suppression de 'JavaScript' : " + langages);
// 5. Parcourir un Set
System.out.println("\nParcours de l'ensemble :");
for (String langage : langages) {
System.out.println("- " + langage);
}
}
}
4. Cas d'usage principal : Supprimer les doublons
L'un des usages les plus courants et puissants de HashSet est de nettoyer une autre collection, typiquement une ArrayList, de ses doublons. Le constructeur de HashSet qui accepte une collection en paramètre fait tout le travail pour vous.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Deduplication {
public static void main(String[] args) {
List<String> listeAvecDoublons = new ArrayList<>(
Arrays.asList("Alice", "Bob", "Charlie", "Alice", "David", "Bob")
);
System.out.println("Liste originale : " + listeAvecDoublons);
// Étape 1 : Créer un HashSet à partir de la liste
// Les doublons sont automatiquement supprimés à la construction.
Set<String> setSansDoublons = new HashSet<>(listeAvecDoublons);
System.out.println("Set (sans doublons) : " + setSansDoublons);
// Étape 2 (optionnelle) : Reconvertir en liste si l'on a besoin des fonctionnalités d'une liste
List<String> listePropre = new ArrayList<>(setSansDoublons);
System.out.println("Liste finale propre : " + listePropre);
}
}
Ce "truc" en deux lignes est un réflexe à avoir pour tout développeur Java.
5. Exercice pratique : Gestion d'une liste d'inscrits
Vous travaillez sur un système d'inscription à un événement. Vous recevez une liste d'adresses e-mail, mais certaines personnes se sont inscrites plusieurs fois. Votre tâche est de produire une liste finale d'inscrits uniques.
Énoncé :
- Créez une classe
GestionInscrits. - Dans la méthode
main, créez uneArrayListdeStringet ajoutez-y les adresses e-mail suivantes :["user1@example.com", "user2@example.com", "user3@example.com", "user1@example.com", "user4@example.com", "user2@example.com"]. - Affichez la liste initiale et sa taille pour constater la présence de doublons.
- Utilisez un
HashSetpour dédoublonner cette liste. - Créez une nouvelle
ArrayListà partir duHashSetpour obtenir la liste finale des inscrits uniques. - Affichez la liste finale et sa taille.
Cliquez ici pour voir une proposition de solution
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class GestionInscrits {
public static void main(String[] args) {
// 1 & 2. Création de la liste initiale avec doublons
List<String> inscriptionsInitiales = new ArrayList<>(
Arrays.asList(
"user1@example.com",
"user2@example.com",
"user3@example.com",
"user1@example.com",
"user4@example.com",
"user2@example.com"
)
);
// 3. Affichage de l'état initial
System.out.println("Inscriptions initiales : " + inscriptionsInitiales);
System.out.println("Nombre total d'inscriptions (avec doublons) : " + inscriptionsInitiales.size());
// 4. Utilisation d'un HashSet pour la déduplication
Set<String> inscritsUniquesSet = new HashSet<>(inscriptionsInitiales);
// 5. Création de la liste finale
List<String> listeFinaleInscrits = new ArrayList<>(inscritsUniquesSet);
// 6. Affichage du résultat final
System.out.println("\nListe des inscrits uniques : " + listeFinaleInscrits);
System.out.println("Nombre d'inscrits uniques : " + listeFinaleInscrits.size());
}
}
Résultat attendu :
Inscriptions initiales : [user1@example.com, user2@example.com, user3@example.com, user1@example.com, user4@example.com, user2@example.com]
Nombre total d'inscriptions (avec doublons) : 6
Liste des inscrits uniques : [user4@example.com, user3@example.com, user2@example.com, user1@example.com]
Nombre d'inscrits uniques : 4
(Note : l'ordre des éléments dans la liste finale peut varier, car il dépend de l'ordre interne non garanti du HashSet.)
Conclusion
Félicitations ! Vous avez découvert HashSet, un outil indispensable pour gérer des collections d'éléments uniques. Grâce à votre connaissance de HashMap, vous comprenez maintenant le mécanisme puissant qui se cache derrière.
Points clés à retenir :
Setest une interface pour les collections sans doublons.HashSetest son implémentation la plus performante, idéale pour la recherche rapide (contains) et l'ajout.- Il garantit l'unicité en utilisant une
HashMapen interne, ce qui rend le contrathashCode()/equals()essentiel pour les objets personnalisés. HashSetne garantit pas l'ordre des éléments.- C'est la solution de choix pour nettoyer rapidement une
Listde ses doublons.
Dans notre prochaine leçon, nous allons généraliser la façon de parcourir toutes ces collections (ArrayList, HashSet, HashMap) en étudiant les itérateurs et en revoyant la boucle for-each sous cet angle. Cela nous permettra d'écrire du code encore plus générique et robuste.