↑
CHAPITRE 8.2

Manipulation des collections

Ajouter, supprimer et parcourir les collections
Cette section montre comment manipuler les collections : ajouter, supprimer, rechercher des éléments, et les parcourir avec des boucles ou des itérateurs.

8.2Manipulation des collections

8.2.1 – Ajout, suppression, recherche

Les collections Java fournissent de nombreuses méthodes pour manipuler les éléments : ajouter, supprimer, rechercher, et bien plus encore.

➕ Ajout d'éléments

Exemple : Ajout dans une List

import java.util.ArrayList;
import java.util.List;

List<String> liste = new ArrayList<>();

// Ajouter des éléments
liste.add("Alice");           // Ajoute Ă  la fin
liste.add("Bob");
liste.add(0, "Charlie");     // Ajoute à l'index 0 (déplace les autres)

System.out.println(liste);   // Affiche : [Charlie, Alice, Bob]

➖ Suppression d'éléments

Exemple : Suppression dans une List

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");

// Supprimer par valeur
liste.remove("Alice");       // Supprime "Alice"

// Supprimer par index
liste.remove(0);             // Supprime l'élément à l'index 0

// Supprimer tous les éléments
liste.clear();                // Vide la liste

🔍 Recherche d'éléments

Exemple : Recherche dans une List

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");

// Vérifier si un élément existe
boolean existe = liste.contains("Alice");  // true

// Trouver l'index d'un élément
int index = liste.indexOf("Bob");           // 1
int dernierIndex = liste.lastIndexOf("Bob"); // Si plusieurs occurrences

// Vérifier si la liste est vide
boolean vide = liste.isEmpty();            // false

// Obtenir la taille
int taille = liste.size();                 // 3

🗺️ Manipulation d'une Map

Exemple : Opérations sur une Map

import java.util.HashMap;
import java.util.Map;

Map<String, Integer> ages = new HashMap<>();

// Ajouter/Modifier
ages.put("Alice", 25);
ages.put("Bob", 30);
ages.put("Alice", 26);       // Remplace la valeur précédente

// Obtenir une valeur
Integer ageAlice = ages.get("Alice");      // 26
Integer ageInconnu = ages.get("Charlie");  // null (clé inexistante)

// Vérifier l'existence d'une clé ou valeur
boolean aClé = ages.containsKey("Alice");  // true
boolean aValeur = ages.containsValue(30); // true

// Supprimer
ages.remove("Bob");                        // Supprime la clé "Bob"
ages.remove("Alice", 25);                  // Supprime seulement si la valeur correspond

// Taille
int taille = ages.size();                 // Nombre de paires clé-valeur

🔢 Manipulation d'un Set

Exemple : Opérations sur un Set

import java.util.HashSet;
import java.util.Set;

Set<String> uniques = new HashSet<>();

// Ajouter
uniques.add("Alice");
uniques.add("Bob");
uniques.add("Alice");        // Ignoré (déjà présent)

// Rechercher
boolean existe = uniques.contains("Alice");  // true

// Supprimer
uniques.remove("Bob");

// Taille
int taille = uniques.size();  // 1

8.2.2 – Parcours d'une collection

Il existe plusieurs façons de parcourir une collection en Java : boucle for-each, Iterator, et méthodes forEach (Java 8+).

🔄 Boucle for-each (recommandée)

La boucle for-each est la méthode la plus simple et la plus lisible pour parcourir une collection.

Exemple : Parcours avec for-each

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");

// Parcours simple
for (String element : liste) {
    System.out.println(element);
}

// Parcours d'une Map
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);

// Parcourir les clés
for (String nom : ages.keySet()) {
    System.out.println(nom);
}

// Parcourir les valeurs
for (Integer age : ages.values()) {
    System.out.println(age);
}

// Parcourir les paires clé-valeur
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
    System.out.println(entry.getKey() + " : " + entry.getValue());
}

🔍 Iterator

L'Iterator permet un contrôle plus fin sur le parcours, notamment pour supprimer des éléments pendant l'itération.

Exemple : Parcours avec Iterator

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");

// Parcours avec Iterator
Iterator<String> it = liste.iterator();
while (it.hasNext()) {
    String element = it.next();
    System.out.println(element);
}

// Supprimer pendant l'itération (sécurisé)
Iterator<String> it2 = liste.iterator();
while (it2.hasNext()) {
    String element = it2.next();
    if (element.equals("Bob")) {
        it2.remove();  // Supprime l'élément courant
    }
}
Important : Utilisez l'Iterator pour supprimer des éléments pendant le parcours. La boucle for-each ne permet pas de modifier la collection pendant l'itération (sauf via Iterator.remove()).

↔️ ListIterator - Parcours bidirectionnel

Le ListIterator est une interface qui étend Iterator et permet de parcourir une liste dans les deux sens (avant et arrière). Il offre également des fonctionnalités supplémentaires comme l'ajout et la modification d'éléments pendant le parcours.

🔑 Caractéristiques de ListIterator

  • Parcours bidirectionnel : Peut parcourir la liste vers l'avant et vers l'arrière
  • Accès Ă  l'index : Permet d'obtenir l'index de l'Ă©lĂ©ment courant
  • Modification : Permet d'ajouter, modifier et supprimer des Ă©lĂ©ments
  • SpĂ©cifique aux List : Fonctionne uniquement avec les implĂ©mentations de List (ArrayList, LinkedList, etc.)

📋 Méthodes principales de ListIterator

Méthodes de parcours :
  • hasNext() : VĂ©rifie s'il y a un Ă©lĂ©ment suivant
  • next() : Retourne l'Ă©lĂ©ment suivant et avance le curseur
  • hasPrevious() : VĂ©rifie s'il y a un Ă©lĂ©ment prĂ©cĂ©dent
  • previous() : Retourne l'Ă©lĂ©ment prĂ©cĂ©dent et recule le curseur
  • nextIndex() : Retourne l'index de l'Ă©lĂ©ment suivant
  • previousIndex() : Retourne l'index de l'Ă©lĂ©ment prĂ©cĂ©dent
Méthodes de modification :
  • add(E element) : Ajoute un Ă©lĂ©ment Ă  la position courante
  • set(E element) : Remplace l'Ă©lĂ©ment retournĂ© par next() ou previous()
  • remove() : Supprime l'Ă©lĂ©ment retournĂ© par next() ou previous()

➡️ Parcours vers l'avant

Exemple : Parcours classique vers l'avant

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");

// Créer un ListIterator
ListIterator<String> it = liste.listIterator();

// Parcourir vers l'avant
while (it.hasNext()) {
    String element = it.next();
    int index = it.previousIndex();  // Index de l'élément courant (après next())
    System.out.println("Index " + index + " : " + element);
}
// Affiche :
// Index 0 : Alice
// Index 1 : Bob
// Index 2 : Charlie

⬅️ Parcours vers l'arrière

Exemple : Parcours vers l'arrière

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");

// Créer un ListIterator positionné à la fin
ListIterator<String> it = liste.listIterator(liste.size());

// Parcourir vers l'arrière
while (it.hasPrevious()) {
    String element = it.previous();
    int index = it.nextIndex();  // Index de l'élément suivant (après previous())
    System.out.println("Index " + index + " : " + element);
}
// Affiche :
// Index 2 : Charlie
// Index 1 : Bob
// Index 0 : Alice

↔️ Parcours dans les deux sens

Exemple : Parcours aller-retour

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");
liste.add("David");

ListIterator<String> it = liste.listIterator();

// Parcourir vers l'avant jusqu'au milieu
System.out.println("Parcours vers l'avant :");
while (it.nextIndex() < liste.size() / 2) {
    System.out.println(it.next());
}
// Affiche : Alice, Bob

// Maintenant parcourir vers l'arrière
System.out.println("\nParcours vers l'arrière :");
while (it.hasPrevious()) {
    System.out.println(it.previous());
}
// Affiche : Bob, Alice

➕ Ajouter des éléments avec ListIterator

Exemple : Ajout d'éléments pendant le parcours

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");

ListIterator<String> it = liste.listIterator();

// Parcourir et ajouter des éléments
while (it.hasNext()) {
    String element = it.next();
    if (element.equals("Bob")) {
        // Ajouter un élément après "Bob"
        it.add("Bobette");  // Ajoute Ă  la position courante
    }
}

System.out.println(liste);
// Affiche : [Alice, Bob, Bobette, Charlie]
Comment fonctionne add() :
  • add() insère l'Ă©lĂ©ment avant l'Ă©lĂ©ment qui serait retournĂ© par next()
  • Après add(), l'appel Ă  next() ne serait pas affectĂ©
  • L'appel Ă  previous() retournerait le nouvel Ă©lĂ©ment ajoutĂ©

✏️ Modifier des éléments avec ListIterator

Exemple : Modification d'éléments

List<String> liste = new ArrayList<>();
liste.add("alice");
liste.add("bob");
liste.add("charlie");

ListIterator<String> it = liste.listIterator();

// Parcourir et mettre en majuscules
while (it.hasNext()) {
    String element = it.next();
    // Modifier l'élément courant
    it.set(element.toUpperCase());  // Remplace l'élément retourné par next()
}

System.out.println(liste);
// Affiche : [ALICE, BOB, CHARLIE]
Important :
  • set() remplace l'Ă©lĂ©ment retournĂ© par le dernier appel Ă  next() ou previous()
  • Vous devez appeler next() ou previous() avant d'appeler set()
  • Vous ne pouvez pas appeler set() après add() ou remove() sans avoir appelĂ© next() ou previous() entre les deux

🗑️ Supprimer des éléments avec ListIterator

Exemple : Suppression pendant le parcours

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");
liste.add("David");

ListIterator<String> it = liste.listIterator();

// Supprimer les éléments qui commencent par "C"
while (it.hasNext()) {
    String element = it.next();
    if (element.startsWith("C")) {
        it.remove();  // Supprime l'élément retourné par next()
    }
}

System.out.println(liste);
// Affiche : [Alice, Bob, David]

📍 Positionnement du ListIterator

Vous pouvez créer un ListIterator à une position spécifique :

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");
liste.add("David");

// Créer un ListIterator à l'index 2 (après "Bob")
ListIterator<String> it = liste.listIterator(2);

// L'élément suivant sera "Charlie"
System.out.println(it.next());  // Affiche : Charlie

// L'élément précédent sera "Bob"
it.previous();
System.out.println(it.previous());  // Affiche : Bob

đź’» Exemple complet : Traitement bidirectionnel

Exemple : Inverser une liste avec ListIterator

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class InversionListe {
    public static void main(String[] args) {
        List<String> liste = new ArrayList<>();
        liste.add("A");
        liste.add("B");
        liste.add("C");
        liste.add("D");
        
        System.out.println("Liste originale : " + liste);
        
        // Inverser avec ListIterator
        ListIterator<String> avant = liste.listIterator();
        ListIterator<String> arriere = liste.listIterator(liste.size());
        
        // Échanger les éléments de chaque côté
        while (avant.nextIndex() < arriere.previousIndex()) {
            String temp = avant.next();
            avant.set(arriere.previous());
            arriere.set(temp);
        }
        
        System.out.println("Liste inversée : " + liste);
        // Affiche : Liste inversée : [D, C, B, A]
    }
}

📊 Comparaison : Iterator vs ListIterator

Fonctionnalité Iterator ListIterator
Parcours avant âś… Oui âś… Oui
Parcours arrière ❌ Non ✅ Oui
Accès à l'index ❌ Non ✅ Oui (nextIndex, previousIndex)
Ajouter des éléments ❌ Non ✅ Oui (add)
Modifier des éléments ❌ Non ✅ Oui (set)
Supprimer des éléments ✅ Oui ✅ Oui
Collections supportées Toutes List uniquement

⚠️ Erreurs communes à éviter

Erreur 1 : Appeler set() sans next() ou previous()
ListIterator<String> it = liste.listIterator();

// ❌ ERREUR : IllegalStateException
// it.set("Nouveau");  // Pas d'élément courant défini

// âś… CORRECT
it.next();  // Définir l'élément courant
it.set("Nouveau");  // Maintenant OK
Erreur 2 : Mélanger add() et remove() sans next()/previous()
ListIterator<String> it = liste.listIterator();
it.next();
it.add("Nouveau");

// ❌ ERREUR : IllegalStateException
// it.remove();  // Pas d'élément courant après add()

// âś… CORRECT
it.next();  // Avancer après add()
it.remove();  // Maintenant OK
Erreur 3 : Utiliser ListIterator sur une collection non-List
Set<String> set = new HashSet<>();

// ❌ ERREUR : Set n'a pas de méthode listIterator()
// ListIterator<String> it = set.listIterator();

// âś… CORRECT : Utiliser Iterator pour Set
Iterator<String> it = set.iterator();

💡 Points clés à retenir

  • ListIterator : Permet le parcours bidirectionnel des listes
  • Parcours avant : hasNext() et next()
  • Parcours arrière : hasPrevious() et previous()
  • Index : nextIndex() et previousIndex() pour obtenir les positions
  • Ajout : add() insère un Ă©lĂ©ment Ă  la position courante
  • Modification : set() remplace l'Ă©lĂ©ment courant
  • Suppression : remove() supprime l'Ă©lĂ©ment courant
  • Positionnement : listIterator(index) crĂ©e un itĂ©rateur Ă  une position spĂ©cifique
  • SpĂ©cifique aux List : Ne fonctionne qu'avec les implĂ©mentations de List
Conseils pratiques :
  • Utilisez ListIterator quand vous avez besoin de parcourir une liste dans les deux sens
  • IdĂ©al pour des algorithmes qui nĂ©cessitent de revenir en arrière (parsing, traitement bidirectionnel)
  • Très utile pour modifier des Ă©lĂ©ments pendant le parcours (mise en majuscules, transformation, etc.)
  • N'oubliez pas d'appeler next() ou previous() avant set() ou remove()
  • Pour un parcours simple en avant, Iterator ou for-each suffisent

🆕 Méthode forEach (Java 8+)

Depuis Java 8, vous pouvez utiliser la méthode forEach avec des expressions lambda.

Exemple : Parcours avec forEach

List<String> liste = new ArrayList<>();
liste.add("Alice");
liste.add("Bob");
liste.add("Charlie");

// forEach avec lambda
liste.forEach(element -> System.out.println(element));

// forEach avec référence de méthode
liste.forEach(System.out::println);

// forEach sur une Map
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);

ages.forEach((nom, age) -> 
    System.out.println(nom + " : " + age)
);

📊 Comparaison des méthodes de parcours

Méthode Avantages Inconvénients Cas d'usage
for-each Simple, lisible, sûr Pas de suppression directe Parcours simple en lecture
Iterator Contrôle fin, suppression sécurisée Plus verbeux Parcours avec suppression
ListIterator Parcours bidirectionnel, ajout/modification Spécifique aux List, plus complexe Parcours avant/arrière, modifications
forEach Concis, fonctionnel Nécessite Java 8+ Parcours avec lambda

💡 Points clés à retenir

  • for-each : MĂ©thode la plus simple pour un parcours en lecture
  • Iterator : Pour un contrĂ´le fin et la suppression sĂ©curisĂ©e
  • ListIterator : Pour un parcours bidirectionnel et des modifications avancĂ©es (List uniquement)
  • forEach : Style fonctionnel avec lambda (Java 8+)
  • Modification : Utilisez Iterator/ListIterator pour modifier pendant le parcours
  • Map : Utilisez keySet(), values(), ou entrySet() pour parcourir