Iteratori și for-each în Java

for-each parcurge pe rând elementele unui array sau ale oricărui Iterable; un Iterator permite și ștergerea în timpul parcurgerii.

Sintaxă
for (Tip element : colectieSauArray) {
    // ...
}

Iterator<Tip> it = colectie.iterator();
while (it.hasNext()) {
    Tip e = it.next();
    // it.remove();
}

Ce face Iteratori și for-each

Bucla for-each (for (String oras : orase)) dă pe rând fiecare element, fără indice. Funcționează cu array-uri și cu orice obiect care implementează Iterable (toate colecțiile: List, Set, cheile unui Map).

În spate, pentru colecții, compilatorul folosește un Iterator: un obiect cu hasNext() (mai sunt elemente?) și next() (dă următorul element). Poți folosi iteratorul direct când ai nevoie de remove(), singura metodă sigură de a șterge elemente în timpul parcurgerii.

Clasele tale pot fi folosite în for-each dacă implementează Iterable<T>, adică au o metodă iterator(). Variabila din for-each este o copie: atribuirea ei nu modifică array-ul sau colecția.

Exemple

Exemplul 1: Parcurgerea unui array, a unei liste și a unui Map

Același tip de buclă merge peste array-uri și colecții. Pentru Map parcurgi entrySet() (sau keySet() / values()).

import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        int[] note = {9, 7, 10};
        int suma = 0;
        for (int nota : note) {
            suma += nota;
        }
        System.out.println("suma notelor: " + suma);

        List<String> orase = List.of("Iași", "Cluj", "Timișoara");
        for (String oras : orase) {
            System.out.println("- " + oras);
        }

        Map<String, Integer> stoc = new TreeMap<>(Map.of("mere", 40, "pere", 12));
        for (Map.Entry<String, Integer> e : stoc.entrySet()) {
            System.out.println(e.getKey() + ": " + e.getValue() + " kg");
        }
    }
}

Rezultat

suma notelor: 26
- Iași
- Cluj
- Timișoara
mere: 40 kg
pere: 12 kg

Exemplul 2: Ștergere în timpul parcurgerii cu Iterator

remove() din for-each aruncă ConcurrentModificationException. Cu iteratorul, it.remove() șterge elementul curent în siguranță.

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

public class Main {
    public static void main(String[] args) {
        List<Integer> note = new ArrayList<>(List.of(9, 4, 10, 3, 7));
        try {
            for (Integer n : note) {
                if (n < 5) {
                    note.remove(n);
                }
            }
        } catch (java.util.ConcurrentModificationException e) {
            System.out.println("for-each + remove: ConcurrentModificationException");
        }

        note = new ArrayList<>(List.of(9, 4, 10, 3, 7));
        Iterator<Integer> it = note.iterator();
        while (it.hasNext()) {
            if (it.next() < 5) {
                it.remove();
            }
        }
        System.out.println("doar note de trecere: " + note);
    }
}

Rezultat

for-each + remove: ConcurrentModificationException
doar note de trecere: [9, 10, 7]

Exemplul 3: O clasă proprie folosită în for-each

Interval implementează Iterable<Integer>, deci poate apărea după : într-un for-each. Iteratorul ține minte poziția curentă.

import java.util.Iterator;
import java.util.NoSuchElementException;

public class Main {
    record Interval(int start, int sfarsit, int pas) implements Iterable<Integer> {
        public Iterator<Integer> iterator() {
            return new Iterator<>() {
                private int curent = start;

                public boolean hasNext() {
                    return curent <= sfarsit;
                }

                public Integer next() {
                    if (!hasNext()) {
                        throw new NoSuchElementException();
                    }
                    int valoare = curent;
                    curent += pas;
                    return valoare;
                }
            };
        }
    }

    public static void main(String[] args) {
        for (int ora : new Interval(8, 20, 4)) {
            System.out.println("autobuz la ora " + ora + ":00");
        }
    }
}

Rezultat

autobuz la ora 8:00
autobuz la ora 12:00
autobuz la ora 16:00
autobuz la ora 20:00

Greșeli frecvente

  • Ștergerea sau adăugarea de elemente în colecție în timpul unui for-each: ConcurrentModificationException. Folosește Iterator.remove sau removeIf.
  • Așteptarea ca for (int n : note) n = 0; să modifice array-ul: n este o copie.
  • Apelarea lui next() de două ori într-un pas al buclei: sari peste elemente.

Comenzi înrudite

  • for: Bucla clasică cu contor: inițializare, condiție verificată înaintea fiecărui pas și actualizare după fiecare pas.
  • Array-uri: Un array păstrează un număr fix de elemente de același tip, accesate prin indice de la 0 la length – 1.
  • Generics (tipuri generice): Generics parametrizează clase și metode cu tipuri (List<String>, Map<String, Integer>), ca erorile de tip să apară la compilare, nu la rulare.
  • Stream-uri (ideea): Un stream descrie o prelucrare de date ca un lanț: sursă, operații intermediare (filter, map, sorted) și o operație finală (toList, count, collect).
  • Clase interne și anonime: O clasă poate fi declarată în altă clasă (statică sau interioară), într-o metodă (locală) sau direct la folosire, fără nume (anonimă).