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.