Architecture et Implémentations de l'Interface Set dans Java

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 à List qui permet la récupération ou la modification d'un élément via son positionnement (get/set), Set ne 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 : Set exclut la méthode listIterator. L'accès aux données s'effectue exclusivement via un objet iterator classique.

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 null au 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);
    }
}

Étiquettes: Java collection-framework Set HashSet LinkedHashSet

Publié le 24 août à 09h12