Différences fondamentales : Set versus Collection et List
L'interface Set représente une collection dépourvue de doublons. En comparaison avec List, elle possède un nombre réduit d'opérations spécifiques :
- Accès par indice : Contrairement à
Listqui permet la récupération ou la modification d'un élément via son positionnement (get/set),Setne supporte pas cet accès séquentiel. - Positionnalité : L'ajout ou la supression ne se fait pas à un index précis dans
Set. - Itérateurs :
Setexclut la méthodelistIterator. L'accès aux données s'effectue exclusivement via un objetiteratorclassique.
Techniquement, Set hérite directement de Collection sans étendre son propre jeu de méthodes. Le comptage total reste identique à celui de sa mère, soit 15 méthodes principales.
Le critère d'équivalence pour ajouter un élément repose sur la méthode equals(). Si un nouvel objet est égal (retour true) à un existant, add() renvoie false. Bien que l'opérateur == puisse indiquer une référence identique, l'identité logique définitive passe par equals().
Implémentation HashSet : Principes de Hashage
La classe HashSet ajoute uniquement la méthode clone() par rapport à la définition de base. Ses caractéristiques majeures incluent :
- Aucune garantie d'ordre lors de l'itération ; la disposition peut varier.
- Nature non synchronisée.
- Acceptation d'une seule valeur
nullau sein du conteneur.
Les opérations standards proviennent de Collection (add, remove, etc.).
package org.dev.collections.impl;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class TestCollectionUnigue {
public static void main(String[] args) {
// Initialisation
Set<integer> setNum = new HashSet<>();
// Insertion
setNum.add(10);
setNum.add(20);
setNum.add(30);
setNum.add(40);
setNum.add(50);
// Suppression
setNum.remove(10);
// Vérification d'appartenance
System.out.println("Présence du 20 : " + setNum.contains(20));
// Parcours via Iterator
Iterator<Integer> it = setNum.iterator();
while(it.hasNext()){
System.out.print(it.next() + ", ");
}
}
}</integer>
Le mécanisme interne repose sur le hachage. Lors de l'insertion, la méthode hashCode() détermine le secteur de stockage. C'est pourquoi la recherche est optimisée. Internement, HashSet utilise une instance de HashMap où les clés sont les éléments du set et les valeurs sont toujours constantes (null).
La condition stricte pour l'équivalence est double : equals() doit retourner vrai ET hashCode() doit être identique. Il est impératif de redéfinir hashCode() chaque fois que equals() est surchargé pour garantir l'intégrité du set.
Implémentation LinkedHashSet : Conservation de l'Ordre
LinkedHashSet étend HashSet tout en maintenant l'ordre d'insertion grâce à un lien doublement chaîné.
Bien qu'il utilise aussi le hashCode pour l'emplacement physique, la structure de liste garantit que le parcours itératif respecte la chronologie des ajouts.
Du point de vue perfomance, l'itération est légèrement plus rapide que HashSet car il n'y a pas besoin de triturer les blocs mémoire aléatoires pour parcourir, mais l'ajout est un peu plus coûteux en raison de la maintenance des références liées.
package org.dev.collections.impl;
import java.util.LinkedHashSet;
import java.util.Iterator;
public class TestOrdreInsertion {
public static void main(String[] args) {
LinkedHashSet<string> liens = new LinkedHashSet<>();
liens.add("Premier");
liens.add("Deuxieme");
liens.add("Troisieme");
liens.add("Quatrieme");
liens.remove("Deuxieme");
System.out.println("Recherche Troisieme : " + liens.contains("Troisieme"));
// Boucle améliorée
Iterator<String> iter = liens.iterator();
while(iter.hasNext()){
String val = iter.next();
System.out.print(val + " ");
}
}
}</string>
Implémentation TreeSet : Gestion du Tri
TreeSet est la seule classe implémentant SortedSet. Elle assure automatiquement le tri croissant des éléments. Deux stratégies existent pour définir l'ordre :
Traitement par défaut (Comparable)
L'objet inséré doit implémenter Comparable. La méthode compareTo(Object o) renvoie :
0: Égalité> 0: Objet actuel supérieur< 0: Objet actuel inférieur
La cohérence impose que si equals est vrai, alors compareTo doit donner 0.
package org.dev.collections.impl;
import java.util.TreeSet;
public class TestTriNaturel {
public static void main(String[] args) {
// Utilisation de la classe personnalisée
TreeSet<Employe> employees = new TreeSet<>();
employees.add(new Employe("Alice", 30));
employees.add(new Employe("Bob", 25));
employees.add(new Employe("Charlie", 25));
for (Employe e : employees) {
System.out.println(e.toString());
}
}
}
class Employe implements Comparable<Employe> {
private final String nom;
private final int age;
public Employe(String nom, int age) {
this.nom = nom;
this.age = age;
}
@Override
public int compareTo(Employe autre) {
// Priorité à l'âge
return Integer.compare(this.age, autre.age);
}
@Override
public String toString() {
return nom + " (" + age + ")";
}
}
Tri personnalisé (Comparator)
Pour éviter de modifier la classe source ou pour changer la logique de tri à la volée, on passe une instance Comparator au constructeur.
package org.dev.collections.impl;
import java.util.Comparator;
import java.util.TreeSet;
public class TestTriExterne {
public static void main(String[] args) {
// Passer le comparateur explicitement
TreeSet<Employe> employees = new TreeSet<>(new ComparateurAgeDecroissant());
employees.add(new Employe("Alice", 30));
employees.add(new Employe("Bob", 25));
for (Employe e : employees) {
System.out.println(e);
}
}
}
class ComparateurAgeDecroissant implements Comparator<Employe> {
@Override
public int compare(Employe e1, Employe e2) {
// Trie de manière décroissante
return Integer.compare(e2.age, e1.age);
}
}