Map<K,V> & ses implémentationsMap<K,V> – Méthodes essentiellesSortedMap<K,V>NavigableMap<K,V>Une table associative (ou map, dictionnaire, tableau associatif) est une structure de données qui stocke des couples (clé → valeur). Chaque clé est unique et permet de retrouver sa valeur de manière efficace.
| Table | Clé (K) | Valeur (V) |
|---|---|---|
| Annuaire téléphonique | Nom & Prénom | Liste de numéros |
| Dictionnaire de langue | Mot | Définition(s) |
| Traducteur | Mot français | Équivalents anglais |
| Cache HTTP | URL | Réponse en cache |
| Config applicative | Clé de propriété | Valeur de propriété |
Conceptuellement, toute table associative expose trois collections :
| Collection | Type Java | Contrainte |
|---|---|---|
| Ensemble des clés | Set<K> | Aucun doublon |
| Collection des valeurs | Collection<V> | Doublons autorisés |
| Ensemble des couples | Set<Map.Entry<K,V>> | Aucun doublon de couple |
equals(). En revanche, une même valeur peut apparaître plusieurs fois associée à des clés différentes.
Les tables associatives forment une branche distincte du Java Collections Framework : elles n'implémentent pas Collection<E> mais leur propre interface racine Map<K,V>.
Map<K,V> – Méthodes essentiellesLes classes HashMap et TreeMap implémentent toutes deux l'interface Map<K,V>. Voici les méthodes fondamentales à connaître.
public interface Map<K, V> {
// ── Taille ─────────────────────────────────────────────
boolean isEmpty();
int size();
// ── Ajout / Modification ────────────────────────────────
V put(K key, V value); // retourne null ou ancienne valeur
void putAll(Map<? extends K, ? extends V> map);
// ── Accès ───────────────────────────────────────────────
V get(Object key); // null si clé absente
V getOrDefault(Object key, V defaultValue);
// ── Suppression ─────────────────────────────────────────
V remove(Object key); // null si clé absente
void clear();
// ── Tests ───────────────────────────────────────────────
boolean containsKey(Object key);
boolean containsValue(Object value);
// ── Vues ────────────────────────────────────────────────
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K,V>> entrySet();
}
| Méthode | Description | Cas particulier |
|---|---|---|
put(k, v) | Associe v à la clé k | Retourne l'ancienne valeur ou null. Si la clé existait, la valeur est écrasée. |
get(k) | Retourne la valeur de la clé k | Retourne null si la clé est absente |
remove(k) | Supprime le couple (k, v) | Retourne l'ancienne valeur ou null |
containsKey(k) | Vérifie la présence d'une clé | Utilise equals() pour la comparaison |
getOrDefault(k, def) | Retourne la valeur ou une valeur par défaut | Pratique pour éviter les NullPointerException |
map.getOrDefault(cle, valeurParDefaut) à un test if (map.containsKey(cle)) suivi de map.get(cle). C'est plus concis et thread-safe.
SortedMap<K,V>L'interface java.util.SortedMap<K,V> (depuis Java 1.2) étend Map<K,V> et définit les fonctionnalités d'une map dont les clés sont triées. L'ordre des clés est déterminé soit par l'implémentation de Comparable<K>, soit par un Comparator<K> fourni à la création.
equals() de la clé doit être cohérente avec compareTo() (ou le comparateur). Sinon, le comportement de la table est indéfini.
public interface SortedMap<K, V> extends Map<K, V> {
K firstKey(); // plus petite clé
K lastKey(); // plus grande clé
SortedMap<K,V> headMap(K toKey); // clés < toKey
SortedMap<K,V> tailMap(K fromKey); // clés >= fromKey
SortedMap<K,V> subMap(K fromKey, K toKey); // [fromKey, toKey[
Comparator<? super K> comparator(); // null si ordre naturel
}
headMap(), tailMap() et subMap() sont des sous-tables actives de la table d'origine : toute modification dans la vue se répercute sur la table maître, et vice-versa.
| HashMap<K,V> | TreeMap<K,V> | |
|---|---|---|
| Structure interne | Table de hachage | Arbre rouge-noir (ABR équilibré) |
| Ordre des clés | Non garanti | Ordre naturel ou Comparator |
| Accès (get/put) | O(1) amorti | O(log n) |
| Itération | Ordre non prévisible | Ordre croissant des clés |
| Clé nulle | 1 clé null autorisée | Interdit (NullPointerException) |
| Interfaces | Map | Map, SortedMap, NavigableMap |
| Quand l'utiliser ? | Performance maximale, ordre sans importance | Quand les clés doivent être triées |
// Constructeur 1 : ordre naturel (Comparable<K> doit être implémenté)
TreeMap<String, Integer> map1 = new TreeMap<>();
// Constructeur 2 : avec Comparator personnalisé
TreeMap<String, Integer> map2 = new TreeMap<>(
Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder())
);
// Constructeur 3 : depuis une Map existante
HashMap<String, Integer> source = new HashMap<>();
// ... remplissage ...
TreeMap<String, Integer> map3 = new TreeMap<>(source);
// Constructeur 1 : par défaut (capacité 16, facteur de charge 0.75)
HashMap<String, Integer> map = new HashMap<>();
// Constructeur 2 : capacité initiale personnalisée
HashMap<String, Integer> map2 = new HashMap<>(100);
HashMap pour la performance. Utilisez TreeMap uniquement si vous avez besoin que les clés soient triées ou si vous utilisez des méthodes de SortedMap/NavigableMap.
Une table associative ne dispose pas d'itérateur direct. On la parcourt via l'une de ses trois vues. Attention : toute modification effectuée via une vue (notamment remove()) est répercutée sur la table maître. En revanche, les ajouts ne sont pas autorisés par les vues.
entrySet()Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Carol", 91);
// ── Java 5+ : for-each sur entrySet ─────────────────────
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
// ── Java 8+ : forEach avec lambda ───────────────────────
scores.forEach((nom, note) ->
System.out.println(nom + " → " + note)
);
keySet()Set<String> cles = scores.keySet();
for (String nom : cles) {
System.out.println(nom);
}
// Sur un TreeMap, les clés sont affichées dans l'ordre naturel
values()Collection<Integer> valeurs = scores.values();
// Collection (pas Set) car les valeurs peuvent contenir des doublons
int somme = valeurs.stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("Somme des notes : " + somme);
iterator.remove()). Toute autre modification lève une ConcurrentModificationException.
Nous allons créer un annuaire qui associe chaque Personne à une liste de numéros de téléphone (ListNo). L'annuaire doit afficher les personnes par ordre alphabétique sur le nom et le prénom, ce qui nous oriente vers un TreeMap.
public class Personne implements Comparable<Personne> {
private final String nom;
private final String prenom;
public Personne(String nom, String prenom) {
this.nom = nom;
this.prenom = prenom;
}
// Getters omis pour la lisibilité
/** Ordre alphabétique : nom puis prénom */
@Override
public int compareTo(Personne autre) {
int cmp = this.nom.compareTo(autre.nom);
return (cmp != 0) ? cmp : this.prenom.compareTo(autre.prenom);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Personne)) return false;
Personne autre = (Personne) obj;
return nom.equals(autre.nom) && prenom.equals(autre.prenom);
}
@Override
public int hashCode() {
return Objects.hash(nom, prenom);
}
@Override
public String toString() {
return nom + " " + prenom;
}
}
public class ListNo {
private final List<String> numeros = new ArrayList<>();
public void ajouter(String numero) { numeros.add(numero); }
@Override
public String toString() { return numeros.toString(); }
}
import java.util.*;
public class TableTelPersonne {
private final TreeMap<Personne, ListNo> annuaire;
/** Constructeur : crée un annuaire vide */
public TableTelPersonne() {
annuaire = new TreeMap<>();
}
/** Ajoute un numéro pour une personne (crée l'entrée si besoin) */
public void ajouterNumero(Personne p, String numero) {
ListNo liste = annuaire.getOrDefault(p, null);
if (liste == null) {
liste = new ListNo();
annuaire.put(p, liste);
}
liste.ajouter(numero);
}
/** Affiche toutes les personnes par ordre alphabétique */
public void afficherPersonnes() {
Set<Personne> cles = annuaire.keySet();
System.out.println(cles);
}
/** Parcourt et affiche chaque couple (personne → numéros) */
public void afficherAnnuaire() {
for (Map.Entry<Personne, ListNo> e : annuaire.entrySet()) {
System.out.println(e.getKey() + " : " + e.getValue());
}
}
/** Recherche les numéros d'une personne */
public ListNo rechercherNumeros(Personne p) {
return annuaire.get(p);
}
}
// ── Utilisation ────────────────────────────────────────────
TableTelPersonne a = new TableTelPersonne();
Personne alice = new Personne("Dupont", "Alice");
Personne bob = new Personne("Martin", "Bob");
a.ajouterNumero(alice, "06 12 34 56 78");
a.ajouterNumero(alice, "01 23 45 67 89");
a.ajouterNumero(bob, "07 98 76 54 32");
a.afficherAnnuaire();
// Dupont Alice : [06 12 34 56 78, 01 23 45 67 89]
// Martin Bob : [07 98 76 54 32]
// ✅ Bon : dépendance à l'interface, pas à l'implémentation
Map<String, Integer> map = new HashMap<>();
// ❌ À éviter : couplage fort sur l'implémentation
HashMap<String, Integer> map = new HashMap<>();
Si vous utilisez vos propres objets comme clés d'un HashMap, vous devez implémenter equals() et hashCode() de manière cohérente. Si a.equals(b) est vrai, alors a.hashCode() == b.hashCode() doit l'être également.
Map<String, Integer> compteur = new HashMap<>();
// Incrémenter un compteur : classique
compteur.put("mot", compteur.getOrDefault("mot", 0) + 1);
// Idem avec merge (Java 8) : plus élégant
compteur.merge("mot", 1, Integer::sum);
| Méthode | Description |
|---|---|
forEach(BiConsumer) | Itération fonctionnelle sur tous les couples |
getOrDefault(k, def) | Retourne la valeur ou une valeur par défaut |
putIfAbsent(k, v) | Ajoute le couple uniquement si la clé est absente |
replace(k, v) | Remplace la valeur uniquement si la clé est présente |
merge(k, v, fn) | Fusionne l'ancienne et la nouvelle valeur via une fonction |
computeIfAbsent(k, fn) | Calcule et ajoute la valeur si la clé est absente |
computeIfPresent(k, fn) | Recalcule la valeur si la clé est présente |
LinkedHashMap<K,V> si vous souhaitez conserver l'ordre d'insertion des entrées, et EnumMap<K,V> pour des performances optimales lorsque les clés sont des valeurs d'une enum.
hashCode()/equals() d'une clé après son insertion dans un HashMap.null dans un TreeMap (lève une NullPointerException).