W3docs

Interface Map en Java

Mappages clé-valeur en Java avec l'interface Map — put, get, remove, keySet, values, entrySet.

Ce chapitre couvre le contrat Map : ses sept méthodes principales, les trois vues qu'il expose pour l'itération, les méthodes par défaut Java 8 qui rendent le code de gestion des maps moderne et concis, les règles de gestion des valeurs null selon l'implémentation, et la façon dont les maps comparent l'égalité. À la fin, vous saurez quels idiomes utiliser et quelle implémentation standard convient à un cas donné.

Map<K, V> est l'autre moitié du framework de collections. Contrairement à l'interface Collection, elle n'étend pas Collection — c'est une hiérarchie séparée, car stocker des clés associées à des valeurs est une abstraction différente de stocker un ensemble d'éléments. En interne, la plupart des implémentations de Set sont simplement des Map où l'on ignore la valeur, donc Map est en quelque sorte la structure primaire et Set est la sœur plus simple.

Le contrat est court : chaque clé est associée à au plus une valeur, les clés forment un ensemble (pas de clés en double), et les valeurs sont une collection arbitraire (les valeurs dupliquées sont permises). Ce qui change entre les implémentations, c'est l'ordre d'itération, la gestion des null, les invariants d'ordre et la sécurité des threads — mais les sept méthodes principales ci-dessous se comportent de la même façon sur toutes.

Les sept méthodes principales

V put(K key, V value);          // insert or overwrite; returns previous value or null
V get(Object key);              // lookup; returns null if missing
V remove(Object key);           // delete; returns previous value or null
boolean containsKey(Object k);  // does the key exist (even if value is null)?
boolean containsValue(Object v); // O(n) scan of values
int size();
boolean isEmpty();

Quelques subtilités à bien intégrer :

  • put retourne la valeur précédente pour cette clé, ou null s'il n'y avait pas de correspondance. C'est ainsi qu'on implémente les idiomes « insérer si absent » — sauf que ce n'est pas nécessaire, car putIfAbsent fait exactement cela et est plus clair.

  • get retournant null signifie soit « la clé n'est pas présente » soit « la clé est présente mais sa valeur est null ». C'est une ambiguïté si votre map autorise les valeurs null ; utilisez containsKey pour lever l'ambiguïté, ou — mieux — utilisez getOrDefault pour fournir une valeur sentinelle :

    int count = counts.getOrDefault("java", 0); // 0 if absent

Les trois vues

Une Map n'est pas itérable directement. Pour itérer, il faut lui demander l'une des trois vues de son contenu :

Set<K>          keys    = map.keySet();
Collection<V>   values  = map.values();
Set<Map.Entry<K, V>> es = map.entrySet();

Ces vues sont vivantes — elles reflètent les modifications apportées à la map sous-jacente, et les modifications effectuées via la vue se propagent en retour. Supprimer une entrée via entrySet() la supprime de la map ; itérer sur keySet() et appeler iterator.remove() supprime l'entrée. On ne peut pas faire add sur keySet ou values (pas de valeur ou de clé avec laquelle faire la paire), mais on peut vider ou supprimer.

L'itération utilise presque toujours entrySet() — obtenir les deux parties de chaque paire en une fois est moins coûteux qu'appeler get(k) pour chaque clé :

for (Map.Entry<String, Integer> e : counts.entrySet()) {
  System.out.println(e.getKey() + " -> " + e.getValue());
}

Ou, la forme lambda ajoutée en Java 8 :

counts.forEach((k, v) -> System.out.println(k + " -> " + v));

Les méthodes par défaut Java 8 qui comptent vraiment

Java 8 a ajouté plusieurs méthodes Map qui prennent une fonction et se comportent de manière atomique. Elles transforment beaucoup de patrons en trois lignes en une seule ligne :

  • getOrDefault(k, def)get(k) mais def au lieu de null.
  • putIfAbsent(k, v)put uniquement si la clé est absente.
  • computeIfAbsent(k, fn) — calcule atomiquement la valeur si absente, la stocke et la retourne. La pierre angulaire de « mémoïser cet appel coûteux » :
    Map<String, List<Order>> byUser = new HashMap<>();
    byUser.computeIfAbsent(order.user(), u -> new ArrayList<>()).add(order);
  • computeIfPresent(k, biFn) — recalcule uniquement si la clé existe déjà. Utile pour les compteurs qui doivent ignorer les clés inconnues.
  • compute(k, biFn) — universel : passe la valeur actuelle (ou null), retourne la nouvelle. Supprime l'entrée si la fonction retourne null.
  • merge(k, v, biFn) — combine une nouvelle valeur avec celle existante le cas échéant. Le compteur classique :
    for (String w : words) {
      counts.merge(w, 1, Integer::sum);   // first time: stores 1; subsequent: adds
    }

Ce sont les opérations qui rendent la gestion moderne des maps en Java concise. Utilisez-les au lieu des paires get/put.

Clés null et valeurs null

Les règles dépendent de l'implémentation :

Classeclé nullvaleur null
HashMapune autoriséeplusieurs autorisées
LinkedHashMapune autoriséeplusieurs autorisées
TreeMapnonplusieurs autorisées
Hashtablenonnon
ConcurrentHashMapnonnon
Map.of(...) (immutable)nonnon

La règle générale pour le nouveau code : ne pas stocker de null dans une map. Utilisez Optional, une valeur sentinelle, ou tout simplement ne pas insérer l'entrée. La fabrique Map.of l'impose pour vous.

Égalité entre implémentations

Deux maps sont equals si leurs entrySet() sont égaux — mêmes clés, mêmes valeurs, quel que soit l'ordre d'itération ou l'implémentation. Un HashMap et un TreeMap avec les mêmes paires clé-valeur sont égaux. C'est la même règle d'« égalité structurelle » que suit Set.

Les implémentations standard, en un coup d'œil

ClasseStructure de baseOrdre d'itérationUtilisation
HashMaptable de hachagenon spécifiéle choix par défaut
LinkedHashMaptable de hachage + liste chaînéeordre d'insertion ou d'accèscaches LRU, itération prévisible
TreeMaparbre rouge-noirtrié par clérequêtes de plage sur les clés, sortie triée
Hashtabletable de hachage, synchroniséenon spécifiéhéritage ; rarement le bon choix
ConcurrentHashMaptable de hachage striéenon spécifiécode multi-threadé
EnumMapindexé par tableau de bitsordre d'enumMap<MyEnum, V>
Map.of(...)immutablenon spécifiépetites maps fixes

Les chapitres suivants couvrent en détail les choix courants : HashMap, LinkedHashMap et TreeMap. ConcurrentHashMap et EnumMap apparaissent dans des parties ultérieures.

Un exemple concret : compteurs, regroupement et les trois vues

Le programme ci-dessous illustre les idiomes modernes des maps — merge pour compter, computeIfAbsent pour regrouper, les trois vues, et la distinction entre getOrDefault et get.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • merge(word, 1, Integer::sum) est le comptage de mots moderne et idiomatique. Pas de get/put/vérification de null nulle part.
  • computeIfAbsent crée la liste vide exactement une fois par clé — une façon propre de construire une Map<K, List<V>> sans parsemer if (m.get(k) == null) m.put(k, new ArrayList<>()) partout.
  • Les trois vues sont des fenêtres vivantes sur la même map ; entrySet() est le moyen le moins coûteux d'itérer quand on a besoin des deux moitiés de chaque paire.
  • getOrDefault supprime la raison la plus courante de vérifier les null. Utilisez-le chaque fois qu'il existe une valeur par défaut sensée.
  • Un HashMap et un TreeMap avec les mêmes entrées sont equals l'un à l'autre ; la seule chose qui change est l'ordre d'itération.

Et ensuite

L'implémentation par défaut — et celle que vous verrez dans 90 % du code Java — est basée sur une table de hachage. HashMap est le chapitre suivant ; nous y couvrirons le tableau de buckets, l'optimisation de treeification de Java 8, et ce qu'il faut faire lorsque vos clés sont vos propres classes.

Pratique

Pratique
`counts` est un `HashMap<String, Integer>`. Quelle ligne est la façon idiomatique d'incrémenter le compteur pour `'java'`, en considérant qu'une clé absente commence à zéro ?
`counts` est un `HashMap<String, Integer>`. Quelle ligne est la façon idiomatique d'incrémenter le compteur pour `'java'`, en considérant qu'une clé absente commence à zéro ?
Was this page helpful?