Table of Contents
Manipuler des cartes de données en Java : une approche pratique
Introduction
Les cartes de données (ou maps) sont une structure de données fondamentale en programmation Java qui permettent de stocker et de recuperer des paires cle-valeur dans un seul conteneur. Contrairement aux listes ou aux tableaux qui utilisent des indices numeriques, les maps permettent d’acceder aux donnees via des cles de n’importe quel type, ce qui les rend extremement polyvalentes.
En Java, l’interface Map<K, V> definit le contrat de base pour toutes les implementations de cartes. Les implementations les plus courantes incluent :
- HashMap : Implementation basee sur une table de hachage, offrant des performances O(1) pour les operations de base
- TreeMap : Implementation basee sur un arbre rouge-noir, maintenant les cles triees
- LinkedHashMap : Combine les avantages de HashMap avec le maintien de l’ordre d’insertion
- ConcurrentHashMap : Version thread-safe pour les environnements multi-threades
Dans cet article, nous allons explorer en profondeur les differentes facons de manipuler les cartes de donnees en Java, depuis les operations de base jusqu’aux methodes fonctionnelles introduites dans Java 8. Nous verrons egalement les bonnes pratiques et les pieges courants a eviter
Creation d’une carte de donnees
La premiere etape pour manipuler une carte de donnees est de la creer. En Java, il existe plusieurs classes qui implementent l’interface Map, notamment HashMap, TreeMap et LinkedHashMap. Dans cet exemple, nous allons utiliser la classe HashMap.
import java.util.HashMap;
import java.util.Map;
public class Exemple {
public static void main(String[] args) {
// Creation d'une carte de donnees HashMap
HashMap<String, Integer> map = new HashMap<>();
// Ajout d'elements a la carte
map.put("John", 20);
map.put("Paul", 30);
map.put("Peter", 40);
}
}
Differentes facons de creer une Map
Java offre plusieurs manieres de creer et d’initialiser une Map :
// 1. Creation standard avec constructeur par defaut
Map<String, Integer> map1 = new HashMap<>();
// 2. Creation avec capacite initiale (optimisation)
Map<String, Integer> map2 = new HashMap<>(100);
// 3. Creation avec capacite et facteur de charge
Map<String, Integer> map3 = new HashMap<>(100, 0.75f);
// 4. Creation a partir d'une autre Map
Map<String, Integer> map4 = new HashMap<>(map1);
// 5. Creation immutable avec Map.of() (Java 9+)
Map<String, Integer> immutableMap = Map.of(
"John", 20,
"Paul", 30,
"Peter", 40
);
// 6. Creation immutable avec Map.ofEntries() pour plus de 10 entrees
Map<String, Integer> largeImmutableMap = Map.ofEntries(
Map.entry("John", 20),
Map.entry("Paul", 30),
Map.entry("Peter", 40)
);
Manipuler les elements de la carte
Une fois que nous avons cree une carte de donnees, nous pouvons manipuler ses elements en utilisant diverses methodes. Voici quelques exemples :
Ajouter des elements a la carte
Nous pouvons ajouter des elements a la carte en utilisant la methode put().
map.put("Jack", 50);
// putIfAbsent n'ajoute que si la cle n'existe pas
map.putIfAbsent("Jack", 100); // Ne change rien car "Jack" existe deja
map.putIfAbsent("Lisa", 25); // Ajoute "Lisa" avec la valeur 25
Recuperer les valeurs d’une cle specifique
Nous pouvons recuperer la valeur associee a une cle specifique en utilisant la methode get().
Integer value = map.get("John");
System.out.println(value); // Affiche 20
// getOrDefault retourne une valeur par defaut si la cle n'existe pas
Integer unknownValue = map.getOrDefault("Unknown", -1);
System.out.println(unknownValue); // Affiche -1
Verifier l’existence de cles ou valeurs
// Verifier si une cle existe
boolean hasJohn = map.containsKey("John"); // true
// Verifier si une valeur existe
boolean hasValue20 = map.containsValue(20); // true
// Verifier si la map est vide
boolean isEmpty = map.isEmpty(); // false
// Obtenir la taille de la map
int size = map.size(); // Nombre d'elements
Modifier ou supprimer des elements de la carte
Nous pouvons modifier ou supprimer des elements de la carte en utilisant les methodes replace() et remove() respectivement.
// Remplacer une valeur
map.replace("Peter", 50);
// Remplacer uniquement si l'ancienne valeur correspond
map.replace("Peter", 50, 60); // Remplace 50 par 60 uniquement si valeur actuelle est 50
// Supprimer une entree
map.remove("Paul");
// Supprimer uniquement si la valeur correspond
map.remove("John", 20); // Supprime uniquement si valeur est 20
Parcourir une Map
// Parcourir les cles
for (String key : map.keySet()) {
System.out.println("Cle: " + key);
}
// Parcourir les valeurs
for (Integer val : map.values()) {
System.out.println("Valeur: " + val);
}
// Parcourir les entrees (cle-valeur)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// Parcourir avec forEach (Java 8+)
map.forEach((key, val) -> System.out.println(key + " = " + val));
Methodes fonctionnelles pour manipuler les Maps (Java 8+)
Les cartes de donnees proposent egalement plusieurs methodes fonctionnelles introduites dans Java 8 pour manipuler leurs elements de maniere plus elegante. Voici les principales :
computeIfAbsent()
Cette methode permet d’ajouter un element a la carte si la cle specifiee n’est pas deja presente. Elle est particulierement utile pour initialiser des valeurs complexes de maniere paresseuse (lazy initialization).
// Cas d'utilisation classique : compteur de mots
Map<String, Integer> wordCount = new HashMap<>();
String[] words = {"apple", "banana", "apple", "cherry", "banana", "apple"};
for (String word : words) {
wordCount.computeIfAbsent(word, k -> 0);
wordCount.put(word, wordCount.get(word) + 1);
}
// Resultat : {apple=3, banana=2, cherry=1}
// Cas d'utilisation avance : Map de listes
Map<String, List<String>> groupedData = new HashMap<>();
groupedData.computeIfAbsent("fruits", k -> new ArrayList<>()).add("apple");
groupedData.computeIfAbsent("fruits", k -> new ArrayList<>()).add("banana");
// Resultat : {fruits=[apple, banana]}
computeIfPresent()
Cette methode permet de modifier un element de la carte uniquement si la cle specifiee est presente et que sa valeur n’est pas null.
Map<String, Integer> scores = new HashMap<>();
scores.put("John", 100);
scores.put("Paul", 80);
// Augmenter le score de 10% si present
scores.computeIfPresent("John", (k, v) -> (int)(v * 1.1));
System.out.println(scores.get("John")); // Affiche 110
// Ne fait rien car "Peter" n'existe pas
scores.computeIfPresent("Peter", (k, v) -> v + 50);
compute()
Cette methode permet de calculer une nouvelle valeur pour une cle, que celle-ci existe ou non. Si la fonction retourne null, l’entree est supprimee.
Map<String, Integer> inventory = new HashMap<>();
inventory.put("apples", 10);
// Ajouter 5 pommes (ou creer l'entree si elle n'existe pas)
inventory.compute("apples", (k, v) -> (v == null) ? 5 : v + 5);
System.out.println(inventory.get("apples")); // Affiche 15
// Creer une nouvelle entree pour les oranges
inventory.compute("oranges", (k, v) -> (v == null) ? 5 : v + 5);
System.out.println(inventory.get("oranges")); // Affiche 5
// Supprimer une entree en retournant null
inventory.compute("apples", (k, v) -> v > 10 ? v - 20 : null);
replaceAll()
Cette methode permet de remplacer toutes les valeurs de la Map en appliquant une fonction.
Map<String, Integer> prices = new HashMap<>();
prices.put("item1", 100);
prices.put("item2", 200);
prices.put("item3", 300);
// Appliquer une reduction de 10% sur tous les prix
prices.replaceAll((key, value) -> (int)(value * 0.9));
// Resultat : {item1=90, item2=180, item3=270}
Fusion et combinaison de Maps
Il est possible de fusionner ou combiner plusieurs cartes de donnees en utilisant les methodes putAll() et merge(). Ces methodes sont essentielles pour agreger des donnees provenant de sources multiples.
putAll()
Cette methode permet d’ajouter tous les elements d’une autre carte a la notre. Attention : les valeurs existantes seront ecrasees.
HashMap<String, Integer> map1 = new HashMap<>();
map1.put("John", 20);
map1.put("Paul", 30);
HashMap<String, Integer> map2 = new HashMap<>();
map2.put("One", 1);
map2.put("Three", 3);
map2.put("John", 100); // Ecrasera la valeur de John dans map1
map1.putAll(map2);
// Resultat : {John=100, Paul=30, One=1, Three=3}
merge()
Cette methode est plus puissante que putAll() car elle permet de definir une strategie de fusion lorsqu’une cle existe deja.
Map<String, Integer> scores = new HashMap<>();
scores.put("John", 100);
scores.put("Paul", 80);
// Si "Kelly" n'existe pas, ajouter 50
// Si "Kelly" existe, additionner les valeurs
scores.merge("Kelly", 50, Integer::sum);
System.out.println(scores.get("Kelly")); // Affiche 50
// Fusionner avec une valeur existante
scores.merge("John", 20, Integer::sum);
System.out.println(scores.get("John")); // Affiche 120 (100 + 20)
// Cas d'utilisation pratique : compteur de mots avec merge
Map<String, Integer> wordCount = new HashMap<>();
String[] words = {"apple", "banana", "apple", "cherry", "banana", "apple"};
for (String word : words) {
wordCount.merge(word, 1, Integer::sum);
}
// Resultat : {apple=3, banana=2, cherry=1}
// Fusionner deux Maps avec une strategie personnalisee
Map<String, Integer> teamA = Map.of("wins", 10, "losses", 5);
Map<String, Integer> teamB = Map.of("wins", 8, "losses", 7);
Map<String, Integer> combined = new HashMap<>(teamA);
teamB.forEach((key, value) -> combined.merge(key, value, Integer::sum));
// Resultat : {wins=18, losses=12}
Bonnes Pratiques
Voici les bonnes pratiques essentielles pour travailler efficacement avec les Maps en Java :
1. Utiliser l’interface plutot que l’implementation
// Bon : utiliser l'interface Map
Map<String, Integer> map = new HashMap<>();
// A eviter : utiliser directement l'implementation
HashMap<String, Integer> map = new HashMap<>();
2. Specifier la capacite initiale pour les grandes Maps
// Si vous savez que la Map contiendra environ 1000 elements
// Capacite = elements / facteur_de_charge = 1000 / 0.75 = 1334
Map<String, Integer> largeMap = new HashMap<>(1400);
3. Privilegier les methodes fonctionnelles (Java 8+)
// Ancien style (verbeux)
if (!map.containsKey("key")) {
map.put("key", new ArrayList<>());
}
map.get("key").add("value");
// Nouveau style (elegant)
map.computeIfAbsent("key", k -> new ArrayList<>()).add("value");
4. Utiliser getOrDefault() pour eviter les null
// A eviter
Integer value = map.get("key");
if (value == null) {
value = 0;
}
// Recommande
Integer value = map.getOrDefault("key", 0);
5. Choisir la bonne implementation
| Implementation | Cas d’utilisation |
|---|---|
HashMap | Usage general, acces rapide O(1) |
LinkedHashMap | Maintenir l’ordre d’insertion |
TreeMap | Cles triees, operations de plage |
ConcurrentHashMap | Environnements multi-threades |
EnumMap | Cles de type enum |
Pieges Courants
Evitez ces erreurs frequentes lors de l’utilisation des Maps :
1. Modifier une Map pendant l’iteration
// ERREUR : ConcurrentModificationException
for (String key : map.keySet()) {
if (someCondition) {
map.remove(key); // Exception !
}
}
// SOLUTION 1 : Utiliser un iterateur
Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
if (someCondition) {
it.remove(); // OK
}
}
// SOLUTION 2 : Utiliser removeIf() (Java 8+)
map.entrySet().removeIf(entry -> someCondition);
2. Oublier d’implementer hashCode() et equals()
// ERREUR : Les objets personnalises ne fonctionnent pas correctement comme cles
class Person {
String name;
int age;
}
Map<Person, String> personMap = new HashMap<>();
personMap.put(new Person("John", 30), "Engineer");
personMap.get(new Person("John", 30)); // Retourne null !
// SOLUTION : Implementer hashCode() et equals()
class Person {
String name;
int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
3. Utiliser des cles mutables
// ERREUR : Ne jamais utiliser des objets mutables comme cles
List<String> mutableKey = new ArrayList<>();
mutableKey.add("item");
map.put(mutableKey, "value");
mutableKey.add("another"); // Modifie le hashCode !
map.get(mutableKey); // Peut retourner null
// SOLUTION : Utiliser des objets immutables comme cles
// String, Integer, enum, ou classes immutables personnalisees
4. Confusion entre null key et absent key
Map<String, String> map = new HashMap<>();
map.put("key", null); // Valeur null autorisee
// Ces deux cas retournent null
String value1 = map.get("key"); // Cle presente, valeur null
String value2 = map.get("unknown"); // Cle absente
// SOLUTION : Utiliser containsKey() pour distinguer
if (map.containsKey("key")) {
// La cle existe (meme si valeur est null)
}
// Ou utiliser getOrDefault() si null n'est pas une valeur valide
String value = map.getOrDefault("key", "default");
5. Performance de containsValue()
// containsValue() est O(n) - a utiliser avec precaution sur les grandes Maps
boolean exists = largeMap.containsValue("searchValue"); // Lent !
// Si vous avez besoin de recherches frequentes par valeur,
// considerez une structure bidirectionnelle ou un index inverse
Map<String, String> keyToValue = new HashMap<>();
Map<String, String> valueToKey = new HashMap<>();
Conclusion
Les Maps en Java sont des structures de donnees puissantes et polyvalentes qui constituent un element essentiel de tout developpeur Java. Dans cet article, nous avons explore :
- Les bases : creation, ajout, recuperation et suppression d’elements
- Les methodes fonctionnelles :
computeIfAbsent(),computeIfPresent(),compute(),merge()etreplaceAll() - La fusion de Maps :
putAll()etmerge()avec strategies personnalisees - Les bonnes pratiques : utilisation des interfaces, capacite initiale, choix d’implementation
- Les pieges courants : modification pendant l’iteration, hashCode/equals, cles mutables
En maitrisant ces concepts, vous serez capable d’utiliser les Maps de maniere efficace et d’eviter les erreurs classiques. Les methodes fonctionnelles introduites dans Java 8 rendent le code plus lisible et plus concis, alors n’hesitez pas a les adopter dans vos projets.
Pour aller plus loin, explorez egalement les implementations specialisees comme EnumMap pour les cles enum, IdentityHashMap pour les comparaisons par reference, ou WeakHashMap pour les caches avec garbage collection automatique.
In-Article Ad
Dev Mode
Tags
Mahmoud DEVO
Senior Full-Stack Developer
I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.
Enjoyed this article?
Subscribe to get more tech content delivered to your inbox.
Related Articles
Manipuler les classes Java avec ASM et Javassist : bytecode, instrumentation et fichiers JAR
Apprenez a manipuler les classes Java avec ASM et Javassist : chargement, modification du bytecode, instrumentation et creation de fichiers JAR.
Synchronisation Java avec AtomicInteger : eviter la contention et optimiser les performances
Decouvrez comment utiliser les types atomiques Java (AtomicInteger, AtomicLong, AtomicReference, AtomicBoolean) pour reduire la contention, eviter les blocages et ameliorer les performances.
Les avantages des flux tampons pour une performance optimale
Ameliorez les performances de votre code Java avec les flux tampons ! Decouvrez comment reduire considerablement le nombre d'appels systeme, optimiser l'utilisation des types primitifs, gerer efficacement la journalisation et iterer sur les Maps de maniere performante.