Menu

Iterare le collection in Java: for-each, Iterator, forEach

I modi per scorrere le collection Java: il ciclo for-each, l'Iterator, i cicli con indice e il metodo forEach, e come rimuovere elementi in sicurezza durante l'iterazione.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Un compito, più strumenti

Hai raccolto dei dati, in un ArrayList, un HashSet o una HashMap, e ora vuoi visitare ogni elemento. Java ti offre diversi modi per farlo, e la scelta dipende da tre cose: se ti serve l'indice, se devi rimuovere elementi a metà ciclo e se preferisci una sintassi a metodo o a ciclo.

La buona notizia: ogni Collection (lista, insieme, coda) supporta lo stesso ciclo for migliorato, quindi una volta imparato puoi iterarle tutte allo stesso modo.

Leggi i due punti come "in": "per ogni lang in langs". Non tocchi mai un indice, quindi non c'è niente da sbagliare.

Il ciclo for-each

Il for migliorato è la scelta predefinita per il classico "fai qualcosa con ogni elemento". Si legge bene e funziona allo stesso modo con tutti i tipi di collection; qui con un HashSet, che non ha affatto indici:

Una cosa da ricordare su un HashSet: non ha un ordine definito, quindi gli elementi possono essere stampati in qualsiasi sequenza. Il for-each li visita comunque tutti esattamente una volta.

Quando ti serve l'indice

Il for-each ti dà l'elemento ma non la sua posizione. Se ti serve davvero l'indice, per numerare le righe o per guardare gli elementi vicini, usa un ciclo con contatore insieme a size() e get(i). Funziona su una List, che è posizionale; insiemi e mappe non hanno indice, quindi questo stile non si applica a loro.

Non usarlo solo per abitudine. Se non usi i per nient'altro che get(i), la versione for-each è più corta e più difficile da sbagliare.

Iterare una Map

Una Map non è una Collection, quindi non puoi usare il for-each direttamente su di essa. Scorri invece una delle sue tre viste. La più comune è entrySet(), che ti restituisce ogni coppia chiave-valore insieme:

Se ti servono solo le chiavi, scorri ages.keySet(); se ti servono solo i valori, scorri ages.values(). Preferisci entrySet() quando ti servono entrambi: scorrere le chiavi e poi chiamare ages.get(key) all'interno fa una seconda ricerca a ogni iterazione senza motivo.

L'Iterator

Il for-each è in realtà zucchero sintattico sopra un Iterator: un oggetto che percorre una collection un elemento alla volta tramite hasNext() e next(). Raramente scrivi questo ciclo a mano, con un'eccezione importante: è il modo sicuro per rimuovere elementi durante l'iterazione.

it.remove() elimina l'elemento restituito dall'ultima chiamata a next(), e l'iteratore resta valido. È l'unico modo ammesso per modificare una collection durante un ciclo manuale.

La trappola: ConcurrentModificationException

Se chiami add o remove sulla collection stessa dentro un ciclo for-each, ottieni una ConcurrentModificationException: l'iteratore si accorge che la collection è cambiata sotto di lui e si rifiuta di continuare. È uno degli errori più comuni per chi inizia.

List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4));
for (int n : nums) {
    if (n % 2 == 0) {
        nums.remove(Integer.valueOf(n));   // throws ConcurrentModificationException
    }
}

La soluzione è quasi sempre removeIf, che esprime l'intenzione in una sola riga e gestisce l'iterazione al posto tuo:

removeIf funziona su qualsiasi Collection, quindi la stessa chiamata ripulisce anche un HashSet.

Il metodo forEach

Ogni collection ha anche un metodo forEach che riceve una lambda e la esegue su ogni elemento. È un'alternativa al ciclo più funzionale, in stile espressione, comoda per brevi istruzioni di una riga:

Nota che Map.forEach riceve direttamente una lambda a due argomenti (key, value), senza bisogno di entrySet(). Usa forEach per effetti collaterali rapidi; torna al normale ciclo for quando il corpo cresce o devi uscire in anticipo con break, cosa che una lambda non può fare.

Prossimo argomento: i metodi

Ora hai raccolto dati nelle collection e li hai percorsi in tutti i modi che Java offre. Il passo successivo è raccogliere il comportamento: scrivere i tuoi metodi per dare un nome a un blocco di logica, passargli degli input e riutilizzarlo. È l'argomento della pagina successiva.

Domande frequenti

Come si scorre una lista in Java?

Il modo più pulito è il for migliorato (for-each): for (String s : list) { ... }. Funziona con qualsiasi Collection: ArrayList, HashSet e così via. Usa un ciclo con indice e get(i) solo quando ti serve davvero la posizione, e un Iterator quando devi rimuovere elementi durante il ciclo.

Come si itera su una HashMap in Java?

Una Map non è una Collection, quindi si scorre una delle sue viste. La scelta abituale è for (Map.Entry<K, V> e : map.entrySet()), che ti dà sia la chiave (e.getKey()) sia il valore (e.getValue()) in un solo passaggio. Puoi anche scorrere map.keySet() per le chiavi o map.values() per i valori.

Perché ottengo una ConcurrentModificationException durante il ciclo?

Hai chiamato add o remove sulla collection mentre un ciclo for-each la stava iterando. Il for-each usa internamente un Iterator, che si accorge che la collection è cambiata strutturalmente. Risolvi rimuovendo tramite il metodo remove() dell'Iterator stesso, oppure chiamando removeIf(...) invece di usare un ciclo.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA