Set en Java - choisir entre HashSet, LinkedHashSet et TreeSet

Denis Ribeiro .

8 novembre 2026

Diagramme de la hiérarchie des classes `LinkedHashSet` en `java`. Montre les relations d'héritage et d'implémentation.

Une liste de tags, d’identifiants ou de rôles ne devrait généralement pas contenir deux fois la même valeur. En Java, l’interface Set répond précisément à ce besoin, mais le choix entre HashSet, LinkedHashSet et TreeSet influence directement l’ordre, les performances et le comportement du programme. Je présente ici les différences essentielles, des exemples concrets et les erreurs à éviter.

Le bon Set dépend surtout de l’ordre et des performances attendus

  • Set interdit les doublons.
  • HashSet privilégie la rapidité sans garantir l’ordre.
  • LinkedHashSet conserve l’ordre d’insertion.
  • TreeSet trie les éléments automatiquement.
  • equals() et hashCode() déterminent l’identité des objets stockés.

Comprendre l’interface Set en Java

Un Set est une collection qui ne peut contenir qu’une seule occurrence d’un même élément. Lorsque j’ajoute deux fois la chaîne "Paris", la seconde insertion ne crée pas une nouvelle valeur. La méthode add() renvoie alors false, ce qui permet de savoir que l’élément était déjà présent.

Set villes = new HashSet<>();

System.out.println(villes.add("Paris")); // true
System.out.println(villes.add("Paris")); // false
System.out.println(villes.size());       // 1

Contrairement à une List, un ensemble ne fournit pas d’accès par position. Il n’existe donc pas de méthode comme get(0). En échange, les opérations comme rechercher une valeur, l’ajouter ou la supprimer sont généralement très efficaces.

Le comportement des doublons repose sur equals() et hashCode(). Pour les types standards comme String, Integer ou UUID, le contrat est déjà correctement implémenté. Avec une classe personnelle, il faut en revanche redéfinir ces deux méthodes de manière cohérente.

Les opérations essentielles

Set langages = new HashSet<>();

langages.add("Java");
langages.add("Python");

boolean present = langages.contains("Java");
langages.remove("Python");

for (String langage : langages) {
    System.out.println(langage);
}

J’utilise souvent contains() avant de modifier une structure métier, mais ce n’est pas toujours nécessaire. Dans beaucoup de cas, il est plus simple d’appeler directement add() ou remove(), puis d’examiner la valeur booléenne retournée. Cela évite une recherche supplémentaire et garde le code plus lisible.

HashSet, LinkedHashSet ou TreeSet

Ces trois implémentations couvrent la majorité des besoins courants. Leur différence la plus visible concerne l’ordre d’itération, mais le coût des opérations et les possibilités de tri comptent tout autant.

Implémentation Ordre des éléments Performance habituelle Cas d’utilisation
HashSet Aucun ordre garanti O(1) en moyenne Déduplication et recherche rapide
LinkedHashSet Ordre d’insertion O(1) en moyenne Déduplication avec affichage prévisible
TreeSet Ordre naturel ou comparateur O(log n) Valeurs triées et recherche par intervalle

HashSet pour la rapidité

HashSet s’appuie sur une table de hachage. C’est généralement mon choix par défaut lorsque l’application a seulement besoin d’éliminer les doublons et de tester rapidement la présence d’une valeur. Il ne faut toutefois jamais dépendre de l’ordre obtenu pendant un parcours.

Set nombres = new HashSet<>();
nombres.add(30);
nombres.add(10);
nombres.add(20);

System.out.println(nombres); // ordre non garanti

Le résultat peut sembler stable pendant les tests, puis changer après une modification du code, du nombre d’éléments ou de la version de Java. Si l’ordre compte, HashSet est le mauvais choix, même si l’affichage paraît correct sur votre machine.

LinkedHashSet pour garder l’ordre d’arrivée

LinkedHashSet conserve l’ordre dans lequel les éléments ont été ajoutés. Il convient très bien à une liste de filtres sélectionnés, à des permissions ou à des valeurs dédupliquées qui doivent rester présentées dans un ordre naturel pour l’utilisateur.

Set extensions = new LinkedHashSet<>();

extensions.add("pdf");
extensions.add("jpg");
extensions.add("pdf");

System.out.println(extensions); // [pdf, jpg]

Cette implémentation consomme un peu plus de mémoire qu’un HashSet, car elle maintient aussi une structure liée entre les entrées. Dans la plupart des applications, ce compromis est raisonnable dès qu’un résultat prévisible améliore la lisibilité ou l’expérience utilisateur.

TreeSet pour trier automatiquement

TreeSet organise les éléments selon leur ordre naturel ou selon un Comparator. Les chaînes sont triées lexicographiquement et les nombres selon leur valeur. Les opérations sont moins rapides que dans un ensemble basé sur le hachage, mais l’ordre est garanti et les recherches par intervalle deviennent possibles.

Set scores = new TreeSet<>();

scores.add(42);
scores.add(8);
scores.add(25);

System.out.println(scores); // [8, 25, 42]

J’évite d’utiliser TreeSet uniquement pour obtenir un affichage trié ponctuel. Dans ce cas, un HashSet puis un tri au moment nécessaire peut être plus adapté. En revanche, si la collection doit rester triée pendant toute sa durée de vie, TreeSet exprime mieux l’intention.

Dédupliquer une collection sans perdre les bonnes informations

L’un des usages les plus pratiques consiste à supprimer les doublons d’une liste. Il suffit de construire un ensemble à partir de la collection existante, puis de choisir l’implémentation selon l’ordre attendu.

List tags = List.of("java", "api", "java", "sécurité");

Set uniques = new LinkedHashSet<>(tags);

System.out.println(uniques); // [java, api, sécurité]

Avec HashSet, les doublons disparaissent mais l’ordre initial peut être perdu. Avec LinkedHashSet, on obtient une déduplication stable, ce qui est souvent le résultat le plus utile pour une interface ou un export. Pour un classement alphabétique, il faut plutôt utiliser TreeSet.

Lire aussi : Or en Java - Évitez les bugs et l'overflow en modélisant bien

Attention aux objets personnalisés

Deux objets ayant les mêmes données ne sont pas automatiquement considérés comme égaux si leur classe n’implémente pas correctement equals() et hashCode(). Cette erreur produit des doublons invisibles dans les ensembles et peut être difficile à repérer lorsque les objets sont affichés de la même manière.

public final class Utilisateur {
    private final long id;

    public Utilisateur(long id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object objet) {
        if (this == objet) return true;
        if (!(objet instanceof Utilisateur autre)) return false;
        return id == autre.id;
    }

    @Override
    public int hashCode() {
        return Long.hashCode(id);
    }
}

La règle est simple, mais importante. Si deux instances sont égales selon equals(), elles doivent retourner le même hashCode. De plus, il vaut mieux éviter de modifier après insertion les champs utilisés pour calculer cette identité, car l’objet peut ensuite devenir impossible à retrouver dans le Set.

Créer des ensembles immuables et sûrs

Lorsque les valeurs ne doivent pas changer, les méthodes Set.of() et Set.copyOf() rendent l’intention très claire. Elles produisent des ensembles non modifiables et refusent les doublons dès la création.

Set rôles = Set.of("ADMIN", "LECTEUR");

Set copie = Set.copyOf(rôles);

Une tentative d’appel à add() ou remove() sur ces ensembles déclenche une UnsupportedOperationException. C’est une protection utile pour les constantes et les paramètres de configuration, mais il ne faut pas les confondre avec une copie défensive destinée à être modifiée plus tard.

Les méthodes Set.of() refusent également les valeurs null. HashSet et LinkedHashSet acceptent généralement un seul élément nul, tandis que TreeSet dépend de son comparateur et de son ordre de comparaison. Dans du code applicatif, je préfère souvent interdire null explicitement afin d’éviter des comportements différents selon l’implémentation choisie.

Pour un accès concurrent, un simple HashSet n’est pas adapté. Selon le scénario, on peut utiliser ConcurrentHashMap.newKeySet() pour un ensemble partagé entre plusieurs threads, ou CopyOnWriteArraySet lorsque les lectures sont très fréquentes et les modifications rares. Ces solutions répondent à des contraintes précises et ne doivent pas être choisies uniquement parce que le mot « concurrent » semble plus sûr.

Les erreurs qui rendent un Set difficile à maintenir

La première erreur consiste à attendre un ordre déterminé d’un HashSet. La deuxième est de modifier la collection pendant un parcours classique, ce qui peut provoquer une ConcurrentModificationException.

Set valeurs = new HashSet<>();
valeurs.add("A");
valeurs.add("B");

Iterator itérateur = valeurs.iterator();

while (itérateur.hasNext()) {
    String valeur = itérateur.next();

    if (valeur.equals("A")) {
        itérateur.remove();
    }
}

Pour filtrer une collection, la méthode removeIf() est souvent plus simple et plus lisible. Avec les flux, on peut aussi créer un nouvel ensemble, mais il faut choisir explicitement l’implémentation si l’ordre compte.

Set filtrées = valeurs.stream()
    .filter(valeur -> valeur.length() > 1)
    .collect(Collectors.toCollection(LinkedHashSet::new));

Enfin, il ne faut pas utiliser un Set quand les doublons sont significatifs ou quand l’accès par position est central. Une playlist, une file d’événements ou une suite de résultats peut nécessiter une List. Le bon critère n’est pas la popularité de la collection, mais la règle métier que le code doit représenter.

Choisir rapidement la bonne implémentation

Dans un nouveau projet, je pars généralement de trois questions simples. Ai-je besoin d’un ordre, les éléments doivent-ils être triés, et la collection sera-t-elle partagée entre plusieurs threads ? Les réponses orientent presque immédiatement le choix.

  • Choisissez HashSet pour une recherche rapide sans ordre requis.
  • Choisissez LinkedHashSet pour conserver l’ordre d’insertion.
  • Choisissez TreeSet pour un ensemble toujours trié.
  • Choisissez Set.of() pour des valeurs fixes et immuables.
  • Choisissez une collection concurrente uniquement si plusieurs threads accèdent réellement aux données.

Ce choix paraît modeste, mais il évite des corrections coûteuses plus tard. Une collection bien choisie rend le contrat du code visible immédiatement, améliore la fiabilité des tests et limite les surprises lors de l’affichage ou de la sérialisation.

Le réflexe qui évite la plupart des mauvais choix

Un Set est avant tout une déclaration d’intention. Il signifie que chaque élément doit être unique, tandis que l’implémentation précise si l’ordre est libre, conservé ou trié.

Pour commencer, utilisez HashSet lorsque seul le caractère unique compte. Passez à LinkedHashSet dès que l’ordre d’arrivée a une valeur fonctionnelle, et à TreeSet lorsque le tri permanent ou les recherches par intervalle justifient leur coût supplémentaire.

Le point le plus souvent oublié reste la cohérence de equals() et hashCode(). Si cette règle est respectée et que l’implémentation correspond au besoin réel, les ensembles Java deviennent une solution simple, rapide et très fiable.

Questions fréquentes

HashSet élimine les doublons avec des performances moyennes en O(1), sans garantir l’ordre. LinkedHashSet conserve l’ordre d’insertion avec un coût similaire, tandis que TreeSet trie les éléments et effectue ses opérations en O(log n).
Construisez un LinkedHashSet à partir de la liste, par exemple avec new LinkedHashSet<>(tags). Les doublons sont supprimés et la première occurrence de chaque valeur reste dans l’ordre initial.
Le Set utilise ces méthodes pour déterminer si deux objets représentent la même valeur. Si deux instances sont égales selon equals(), elles doivent retourner le même hashCode(). Les champs utilisés pour cette identité ne doivent pas être modifiés après l’insertion.
Set.of() et Set.copyOf() conviennent aux ensembles qui ne doivent pas changer, car ils sont non modifiables. Ils refusent les doublons et les valeurs null, et toute tentative d’ajout ou de suppression déclenche une UnsupportedOperationException.
Évaluer l'article

Moyenne: 0.0 / 5 · 0 évaluations

Tags

collections hashset linkedhashset treeset immutabilité
Autor Denis Ribeiro
Denis Ribeiro
Je m'appelle Denis Ribeiro et j'ai quatre ans d'expérience dans le domaine de la technologie, en particulier dans les domaines du web, de l'intelligence artificielle, des réseaux et de la sécurité. Mon intérêt pour ces sujets a commencé dès mon plus jeune âge, lorsque j'ai découvert à quel point la technologie pouvait transformer notre quotidien. J'aime expliquer des concepts complexes de manière simple, afin d'aider mes lecteurs à mieux comprendre les enjeux actuels et futurs de notre monde numérique. Dans mes écrits, je m'efforce de fournir des informations utiles, précises et à jour. Je prends le temps de vérifier mes sources et de comparer les informations, tout en suivant les dernières tendances du secteur. Mon objectif est de rendre l'apprentissage accessible à tous, en organisant mes connaissances de manière claire et structurée, afin que chacun puisse naviguer avec confiance dans cet univers en constante évolution.
Commentaires (0)
Ajouter un commentaire