Map Java - quelle implémentation choisir ?

Alfred Jacques .

3 novembre 2026

Illustration d'une carte Java montrant le hachage de clés (1, 2070, 2068) vers des indices (0, 1, 2068) dans un tableau, avec des listes chaînées pour gérer les collisions.

Quand une application doit relier un identifiant à une valeur, une ville à son code postal ou un utilisateur à ses droits, une simple liste devient vite peu pratique. Une carte Java permet de retrouver chaque donnée à partir d’une clé, mais le choix entre HashMap, LinkedHashMap, TreeMap et les variantes concurrentes change beaucoup le comportement du programme. Je présente ici les bases, les différences concrètes, les erreurs fréquentes et les bonnes pratiques utiles dans un projet réel.

La bonne implémentation dépend surtout de l’usage prévu

  • Map associe une clé unique à une valeur.
  • HashMap convient dans la majorité des cas sans besoin d’ordre.
  • LinkedHashMap conserve l’ordre d’insertion, tandis que TreeMap trie les clés.
  • ConcurrentHashMap est préférable lorsqu’une même structure est utilisée par plusieurs threads.
  • equals() et hashCode() déterminent si deux clés sont considérées comme identiques.

Schéma d'une carte Java (HashMap) montrant des buckets (tableau) contenant des listes chaînées de paires clé-valeur. Un bucket est réservé pour une clé nulle.

Comprendre le fonctionnement d’une Map en Java

Une Map stocke des associations entre une clé et une valeur. La clé est unique, alors que plusieurs clés peuvent pointer vers la même valeur. Si j’ajoute une nouvelle valeur avec une clé déjà présente, Java remplace l’ancienne valeur au lieu de créer un doublon.

Map ages = new HashMap<>();

ages.put("Camille", 28);
ages.put("Nora", 34);
ages.put("Camille", 29);

System.out.println(ages.get("Camille")); // 29

Le type Map est une interface. Je recommande donc de déclarer la variable avec cette interface et de choisir l’implémentation uniquement au moment de l’instanciation. Cette habitude rend le code plus facile à faire évoluer si les besoins changent.

Map pays = new HashMap<>();

Les opérations essentielles sont put() pour ajouter ou modifier, get() pour lire, remove() pour supprimer et containsKey() pour vérifier l’existence d’une clé. Pour parcourir les associations, entrySet() est généralement le choix le plus clair et le plus efficace.

for (Map.Entry entree : pays.entrySet()) {
    System.out.println(entree.getKey() + " : " + entree.getValue());
}

Choisir entre HashMap, LinkedHashMap et TreeMap

Ces trois classes répondent à des besoins différents. Dans un projet classique, je commence par HashMap, puis je change d’implémentation seulement si l’ordre ou le tri fait partie du besoin fonctionnel. Utiliser une structure triée sans raison ajoute une contrainte inutile.

Implémentation Ordre des éléments Performance habituelle Cas d’usage
HashMap Non garanti Accès moyen en O(1) Recherche rapide par clé
LinkedHashMap Insertion ou accès Accès moyen en O(1) Résultats prévisibles, cache simple
TreeMap Clés triées O(log n) Intervalles, ordre naturel, navigation

HashMap pour les recherches rapides

HashMap s’appuie sur une table de hachage. Elle offre généralement des recherches, insertions et suppressions rapides, à condition que les clés disposent d’un bon contrat entre equals() et hashCode(). L’ordre d’affichage ne doit jamais être utilisé comme une information fiable.

Cette classe accepte les clés et valeurs nulles, avec certaines limites pratiques. Pour une configuration, un index en mémoire ou un regroupement de données, c’est souvent le choix le plus simple et le plus efficace.

LinkedHashMap quand l’ordre compte

LinkedHashMap conserve par défaut l’ordre d’insertion. Cela est utile pour produire une sortie stable, respecter l’ordre d’un fichier importé ou construire un cache rudimentaire en mode accès récent. Le coût mémoire est un peu supérieur à celui d’une HashMap, car la structure maintient aussi des liens entre les entrées.

TreeMap pour exploiter le tri

TreeMap organise les clés selon leur ordre naturel ou selon un Comparator. Je la choisis lorsque le programme doit récupérer les clés avant ou après une valeur, parcourir une plage ou afficher systématiquement les éléments dans l’ordre.

Map priorites = new TreeMap<>();

priorites.put(3, "Normale");
priorites.put(1, "Urgente");
priorites.put(2, "Haute");

System.out.println(priorites); // {1=Urgente, 2=Haute, 3=Normale}

Le tri a un prix. Pour une simple lecture par identifiant, TreeMap est souvent surdimensionnée. Il faut également vérifier que les clés sont comparables entre elles ou fournir un comparateur cohérent.

Manipuler les valeurs sans introduire de bugs

La différence entre une clé absente et une clé présente avec une valeur null peut devenir importante. Un appel à get() renvoie null dans les deux cas avec une HashMap, ce qui rend containsKey() utile lorsque cette distinction compte.

if (ages.containsKey("Camille")) {
    Integer age = ages.get("Camille");
    System.out.println(age);
}

Pour fournir une valeur par défaut, getOrDefault() évite souvent un test verbeux. Pour créer une valeur uniquement si la clé est absente, putIfAbsent() est plus explicite qu’une succession de lectures et d’écritures.

int score = scores.getOrDefault("Nora", 0);
scores.putIfAbsent("Nora", 10);

Les méthodes de calcul sont pratiques

Les méthodes computeIfAbsent(), computeIfPresent() et merge() sont particulièrement utiles pour regrouper des données. Elles réduisent le risque d’oublier un cas limite et rendent l’intention visible directement dans le code.

Map> articlesParAuteur = new HashMap<>();

articlesParAuteur
    .computeIfAbsent("Nora", cle -> new ArrayList<>())
    .add("Guide Java");

Pour compter des occurrences, merge() est souvent très lisible.

Map occurrences = new HashMap<>();

for (String mot : mots) {
    occurrences.merge(mot, 1, Integer::sum);
}

Je conseille aussi de parcourir les entrées avec entrySet() plutôt que de récupérer chaque clé puis de refaire un get(). La différence est modeste sur une petite collection, mais la première approche exprime mieux l’intention et évite une recherche supplémentaire.

Les clés, equals et hashCode décident du résultat

Une clé n’est pas seulement une valeur rangée dans une table. Pour les structures basées sur le hachage, Java utilise hashCode() pour trouver une zone probable, puis equals() pour vérifier l’égalité. Si ces deux méthodes sont incohérentes, la carte peut contenir des entrées qui semblent impossibles à retrouver.

Une règle simple s’applique toujours. Si deux objets sont égaux selon equals(), ils doivent retourner le même résultat avec hashCode(). Dans une classe métier utilisée comme clé, je préfère généralement des objets immuables, dont les champs déterminants ne changent plus après l’insertion.

record UtilisateurKey(String region, long id) {}

Map utilisateurs = new HashMap<>();
utilisateurs.put(new UtilisateurKey("FR", 42), "Compte actif");

String statut = utilisateurs.get(new UtilisateurKey("FR", 42));

Modifier une clé après son insertion est une erreur classique. Si un champ utilisé par hashCode() change, l’entrée reste physiquement dans l’ancienne position, mais une recherche avec la nouvelle valeur peut échouer. Les record sont intéressants dans ce scénario parce qu’ils fournissent automatiquement une égalité et un hachage fondés sur leurs composants.

Lire aussi : Héritage Java - Maîtrisez `extends` pour un code robuste

Ne pas confondre clé et valeur

Dans un dictionnaire de produits, le numéro de référence doit être la clé si l’application recherche principalement un produit à partir de ce numéro. Le nom ou le prix deviennent alors des valeurs. Inverser ce choix complique les recherches et peut provoquer des remplacements inattendus lorsque deux produits partagent la même valeur.

Une carte ne convient pas non plus lorsqu’une clé doit correspondre à plusieurs éléments indépendants. Dans ce cas, la valeur peut être une List ou un Set, comme dans Map>. Il faut simplement accepter que l’on construit alors une relation entre une clé et une collection.

Gérer l’ordre, la nullité et l’immutabilité

Beaucoup de bugs viennent d’une attente implicite concernant l’ordre. Une HashMap peut sembler conserver l’ordre d’insertion pendant un test, puis produire une séquence différente après une modification, une autre version du JDK ou un changement de capacité interne. Si l’ordre est visible par l’utilisateur ou utilisé dans un export, il faut le déclarer avec LinkedHashMap ou TreeMap.

Pour une configuration qui ne doit plus être modifiée, les fabriques Map.of() et Map.copyOf() offrent une solution compacte. Elles produisent des cartes non modifiables et refusent les clés ou valeurs nulles. Ce comportement est volontaire et permet de détecter plus tôt une donnée invalide.

Map constantes = Map.of(
    "environnement", "production",
    "region", "eu-west"
);

Une vue non modifiable et une copie indépendante ne sont pas toujours la même chose. Une vue peut continuer à refléter les changements de la structure originale, tandis qu’une copie ne dépend plus de cette structure. Le choix dépend donc de la durée de vie des données et de la personne qui en contrôle la modification.

Dans les API publiques, je préfère exposer Map plutôt qu’une classe concrète. Cela limite le couplage et laisse la possibilité de changer l’implémentation interne sans modifier les appelants.

Utiliser une Map dans un programme multithread

HashMap n’est pas conçue pour coordonner plusieurs écritures concurrentes. Une lecture simultanée peut être acceptable dans certaines situations si la structure est déjà publiée de manière sûre et n’est plus modifiée, mais les mises à jour concurrentes exigent une vraie stratégie de synchronisation.

ConcurrentHashMap est adaptée lorsque plusieurs threads doivent lire et modifier la même structure. Elle fournit des opérations atomiques comme putIfAbsent(), compute() et merge(), et elle n’accepte ni clé null ni valeur null. Cette restriction évite de confondre une absence de résultat avec une valeur explicitement nulle.

ConcurrentMap visites = new ConcurrentHashMap<>();

visites.merge("accueil", 1L, Long::sum);

Il ne suffit toutefois pas de remplacer automatiquement HashMap par ConcurrentHashMap. Si une opération comprend plusieurs étapes qui doivent rester cohérentes, il faut utiliser les méthodes atomiques appropriées ou protéger l’ensemble avec un mécanisme adapté. La sécurité thread-safe ne rend pas toutes les séquences métier atomiques.

Pour un besoin très spécifique, ConcurrentSkipListMap peut fournir une structure triée et concurrente, mais elle est plus spécialisée. Dans la plupart des services, le choix raisonnable reste simple: structure immuable si possible, HashMap en local, ConcurrentHashMap pour l’état partagé.

Les réflexes qui rendent une Map fiable

  • Déclarer les variables avec Map plutôt qu’avec HashMap lorsque l’implémentation n’a pas besoin d’être exposée.
  • Choisir LinkedHashMap si un ordre stable est attendu par l’utilisateur ou par un test.
  • Choisir TreeMap uniquement lorsque le tri ou les recherches par intervalle sont réellement nécessaires.
  • Ne jamais modifier les champs utilisés par equals() et hashCode() pendant qu’un objet sert de clé.
  • Utiliser containsKey() lorsque la différence entre absence et valeur null est importante.
  • Préférer getOrDefault(), putIfAbsent() et merge() aux séquences manuelles répétitives.
  • Ne pas partager une HashMap modifiable entre threads sans protection explicite.

Mon conseil le plus concret est de partir du besoin observable, pas du nom de la classe. Demandez-vous si l’application doit seulement retrouver une valeur, préserver l’ordre, trier les clés, accepter des valeurs nulles ou supporter des écritures concurrentes. Une fois cette réponse claire, le choix de la structure devient presque mécanique et le code gagne immédiatement en lisibilité.

Une carte bien choisie évite surtout les mauvaises surprises

Pour une recherche rapide par identifiant, HashMap reste le choix par défaut. Ajoutez LinkedHashMap lorsque l’ordre compte, passez à TreeMap lorsque le tri apporte une vraie fonctionnalité et utilisez ConcurrentHashMap seulement pour un état partagé entre threads.

La performance n’est qu’une partie du sujet. La fiabilité dépend tout autant de l’égalité des clés, de leur immutabilité, de la gestion des valeurs absentes et de la clarté de l’API. C’est cette combinaison qui transforme une simple association clé-valeur en structure solide pour une application Java.

Questions fréquentes

HashMap convient aux recherches rapides sans ordre garanti. LinkedHashMap conserve l’ordre d’insertion ou d’accès, tandis que TreeMap trie les clés et permet les recherches par intervalle, avec des opérations en O(log n).
HashMap utilise hashCode() pour localiser une entrée, puis equals() pour vérifier l’égalité. Deux objets égaux doivent avoir le même hashCode(), et les champs utilisés par ces méthodes ne doivent pas changer après l’insertion. Les record sont utiles pour créer des clés immuables.
ConcurrentHashMap est adaptée aux lectures et écritures concurrentes. Elle propose notamment putIfAbsent(), compute() et merge(), mais n’accepte ni clé ni valeur null. Pour une opération composée de plusieurs étapes, il faut tout de même garantir l’atomicité de la séquence métier.
Avec HashMap, get() renvoie null dans les deux cas. Utilisez containsKey() lorsque cette distinction est importante. Pour des données non modifiables, Map.of() et Map.copyOf() créent des cartes qui refusent les clés et valeurs nulles.
Évaluer l'article

Moyenne: 0.0 / 5 · 0 évaluations

Tags

treemap immutabilité hashmap linkedhashmap concurrenthashmap
Autor Alfred Jacques
Alfred Jacques
Je m'appelle Alfred Jacques et j'ai accumulé 12 ans d'expérience dans le domaine des technologies, en particulier dans les secteurs du web, de l'intelligence artificielle, des réseaux et de la sécurité. Mon intérêt pour ces sujets a débuté dès mon adolescence, lorsque j'ai découvert les possibilités infinies qu'offrent les nouvelles technologies. J'aime explorer les enjeux complexes de ces domaines et partager des informations claires et accessibles pour aider mes lecteurs à mieux comprendre les défis et les évolutions technologiques. Dans mes écrits, je m'efforce de fournir des analyses précises et à jour, en vérifiant mes sources et en comparant différentes perspectives. Je m'engage à simplifier des concepts parfois difficiles afin de rendre l'information utile et compréhensible pour tous. Que ce soit en suivant les dernières tendances ou en organisant mes connaissances de manière claire, je souhaite que mes articles soient une ressource précieuse pour ceux qui s'intéressent à la technologie et à ses implications dans notre quotidien.
Commentaires (0)
Ajouter un commentaire