Menu

Ciclo for-each in Java: sintassi, esempi e trappole

Il ciclo for-each di Java (for migliorato) spiegato: iterazione pulita su array e collezioni, quando usarlo e la trappola della modifica in cui cadono tutti.

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

Quando l'indice è solo d'intralcio

Un ciclo for con contatore ti dà un contatore, una condizione e un passo di aggiornamento. Ma moltissime volte la posizione di un elemento non ti interessa davvero: vuoi solo fare qualcosa con ciascuno, in ordine, dall'inizio alla fine. Gestire un indice per questo è lavoro inutile, ed è proprio lì che si infilano gli errori di uno.

Il ciclo for-each (il nome ufficiale in Java è for migliorato) elimina del tutto il contatore. Dai un nome a una variabile, la colleghi a una collezione e il ciclo ti consegna un elemento alla volta.

Sintassi di base

La forma è for (Type element : collection). Leggi i due punti come la parola "in":

Non ci sono i, scores.length o scores[i]. A ogni passaggio, score è l'elemento successivo. Il ciclo viene eseguito una volta per elemento e si ferma da solo quando non ce ne sono più: non puoi andare oltre la fine né partire un elemento troppo presto.

Scorrere una List

Lo stesso ciclo funziona su qualsiasi cosa iterabile, comprese List, Set e gli altri tipi di collezione. Il tipo dell'elemento va prima del nome della variabile:

Nota che non hai dovuto sapere se langs è basata su un array, una lista concatenata o altro: il for-each funziona allo stesso modo su tutte. È questa la sua vera forza: un'unica sintassi leggibile per ogni collezione.

var ti risparmia il nome del tipo

Se il tipo dell'elemento è lungo o ovvio, var lascia che sia il compilatore a dedurlo, così non ti ripeti:

Senza var, quella variabile del ciclo sarebbe il lungo Map.Entry<String, Integer>. var mantiene il codice leggibile, e il tipo viene comunque controllato completamente in fase di compilazione: non è un tipo dinamico e permissivo.

La trappola della modifica

Ecco la regola in cui cadono tutti: non puoi aggiungere o rimuovere elementi da una collezione mentre un ciclo for-each la sta percorrendo. Farlo lancia una ConcurrentModificationException:

List<String> items = new ArrayList<>(List.of("a", "b", "c"));

for (String item : items) {
    if (item.equals("b")) {
        items.remove(item);   // lancia ConcurrentModificationException
    }
}

Il ciclo si accorge che la lista è cambiata sotto di lui e si interrompe, invece di saltare o ripetere elementi in silenzio. Per rimuovere in sicurezza, passa a un Iterator esplicito, che ha un remove() di cui il ciclo è al corrente:

Una scorciatoia comune è items.removeIf(item -> item.equals("b")), che usa una lambda expression per fare la stessa cosa in una riga.

Solo lettura, niente riassegnazione

Un altro limite sottile: assegnare un valore alla variabile del ciclo cambia solo la copia locale, non la collezione. Questo sorprende chi arriva da linguaggi in cui la variabile del ciclo è un riferimento diretto:

Se devi riscrivere nell'array, ti serve l'indice, e quindi il classico ciclo for con contatore: for (int i = 0; i < nums.length; i++) nums[i] = nums[i] * 10;. Con elementi di tipo oggetto puoi modificare l'oggetto a cui punta la variabile (chiamando un setter, per esempio), ma non puoi sostituirlo nella collezione.

break e continue funzionano ancora

Un for-each è un vero ciclo, quindi break e continue si comportano esattamente come altrove: break esce dal ciclo, continue salta all'elemento successivo:

Questo stampa keep e poi keep: salta "skip" e si ferma a "stop" prima di arrivare a "never". Quindi non sei obbligato a visitare ogni elemento; rinunci solo all'indice in cambio di un codice più pulito.

Prossimo passo: gli array

Hai già scorso degli array un paio di volte senza soffermarti su cosa siano davvero: contenitori a dimensione fissa, con indici e quel campo .length. La prossima pagina riparte dall'inizio e tratta gli array come si deve: come dichiararli, i valori predefiniti con cui partono e in cosa la loro dimensione fissa differisce da un ArrayList che può crescere.

Domande frequenti

Cos'è un ciclo for-each in Java?

Un ciclo for-each (chiamato anche for migliorato) percorre ogni elemento di un array o di una collezione senza contatore: for (Type item : collection) { ... }. Leggilo come "per ogni item in collection". È più pulito di un ciclo for con contatore quando ti serve solo ogni elemento e mai l'indice.

Qual è la differenza tra un ciclo for e un ciclo for-each in Java?

Il classico ciclo for usa un contatore esplicito (for (int i = 0; i < arr.length; i++)), quindi controlli tu l'indice e la direzione. Il ciclo for-each, for (Type x : arr), non ha indice: visita ogni elemento in ordine. Usa il for-each per passaggi in sola lettura dall'inizio alla fine; usa il ciclo con contatore quando ti serve l'indice, vuoi saltare elementi o modificare la struttura della collezione.

Perché il mio ciclo for-each lancia ConcurrentModificationException?

Perché hai chiamato add() o remove() sulla collezione mentre la scorrevi con un for-each. Il ciclo rileva la modifica strutturale e lancia un'eccezione per proteggerti da un comportamento indefinito. Per rimuovere elementi in sicurezza, usa un Iterator esplicito e il suo metodo remove(), oppure raccogli gli elementi da eliminare e rimuovili dopo il ciclo.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA