CodeGym /Corsi /JAVA 25 SELF /Iterable e Iterator: scorrere le collezioni

Iterable e Iterator: scorrere le collezioni

JAVA 25 SELF
Livello 27 , Lezione 1
Disponibile

1. Interfaccia Iterable

In Java quasi tutte le collezioni (tranne Map) implementano l'interfaccia Iterable. Significa che si possono attraversare in sequenza — elemento dopo elemento — senza entrare nei dettagli della struttura interna. Per uno sviluppatore è come dire: «la collezione ha un modo integrato per scorrere tutti gli elementi».

L'interfaccia Iterable definisce esattamente un solo metodo:

Iterator<E> iterator();

Il metodo iterator() restituisce un oggetto di tipo Iterator — un «assistente» che sa come attraversare la collezione passo dopo passo. Grazie a ciò funziona il consueto ciclo for-each:

for (ElementType e : collection) {
    // ...
}

— dietro le quinte c'è proprio quell'Iterator. Un ulteriore vantaggio: con il suo aiuto si possono rimuovere elementi in modo sicuro durante l'iterazione tramite il metodo remove(). Se si prova a farlo con un semplice ciclo, si rischia facilmente una ConcurrentModificationException.

2. Interfaccia Iterator

Iterator è un «corriere» che sa percorrere la tua collezione senza saltare elementi e rispettando il suo ordine di attraversamento.

Metodo Descrizione
boolean hasNext()
Ci sono altri elementi da scorrere?
E next()
Restituisce l'elemento successivo e ci avanza
void remove()
Elimina in modo sicuro l'elemento corrente, senza errori

Esempio di iterazione di una collezione con Iterator

import java.util.*;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>();
        tasks.add("Accarezzare il gatto");
        tasks.add("Fare i compiti");
        tasks.add("Guardare una serie");

        Iterator<String> it = tasks.iterator();
        while (it.hasNext()) {
            String task = it.next();
            System.out.println("Attività: " + task);
        }
    }
}

Cosa sta succedendo qui?

  • Otteniamo l'iterator tramite tasks.iterator().
  • Finché esiste un elemento successivo (hasNext() restituisce true), lo prendiamo con next() e lo stampiamo.
  • L'iterator gestisce da solo l'ordine di attraversamento — non devi sapere come la collezione memorizza gli elementi internamente.

3. Perché serve Iterator se esistono i cicli?

Con Iterator puoi scorrere qualsiasi collezione, anche senza indici (ad esempio, Set). È un modo universale, indipendente dal tipo specifico di collezione.

Rimozione sicura degli elementi

Un compito comune: attraversare una collezione ed eliminare alcuni elementi. Se lo fai con un for-each, puoi ottenere un errore:

for (String task : tasks) {
    if (task.contains("gatto")) {
        tasks.remove(task); // BOOM! ConcurrentModificationException
    }
}

Perché succede? La collezione non si aspetta che la sua struttura venga modificata direttamente durante l'attraversamento iniziato dall'iterator.

Modo corretto:

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("gatto")) {
        it.remove(); // Andrà tutto liscio!
    }
}

Perché non si possono semplicemente usare gli indici?

Perché non tutte le collezioni hanno indici. Ad esempio, in HashSet o TreeSet non esiste il concetto di «quinto elemento». Iterator funziona sempre — questa è la sua forza.

4. Dettagli su for-each

Il ciclo for migliorato (for-each) è apparso già con Java 5. In sostanza è zucchero sintattico che permette di scorrere gli elementi nel modo più semplice:

for (String task : tasks) {
    System.out.println("Attività: " + task);
}

Sotto il cofano il compilatore chiama iterator(), controlla gli elementi con hasNext() e li estrae con next(). Si legge letteralmente come una frase in linguaggio naturale: «per ogni attività dell'elenco».

Quando il for-each non va bene?

  • Devi eliminare elementi durante l'iterazione (for-each non permette di chiamare remove() direttamente).
  • Serve l'accesso all'indice, per esempio per sostituire un elemento in una certa posizione.
  • Stai lavorando con Map — è composta da coppie «chiave-valore», e per scorrerla serve una logica propria.

5. Iterare una Map: trucchi e particolarità

L'interfaccia Map non implementa Iterable direttamente, in quanto è un insieme di coppie «chiave-valore». Tuttavia, Map fornisce viste comode per l'iterazione.

Iterazione per chiavi

Map<String, String> users = new HashMap<>();
users.put("vasya", "vasya@example.com");
users.put("petya", "petya@gmail.com");

for (String login : users.keySet()) {
    System.out.println("Login: " + login);
}

Iterazione per valori

for (String email : users.values()) {
    System.out.println("Email: " + email);
}

Iterazione per coppie (chiave-valore)

Il modo più universale è scorrere entrySet():

for (Map.Entry<String, String> entry : users.entrySet()) {
    System.out.println("Login: " + entry.getKey() + ", Email: " + entry.getValue());
}

Curiosità: Entry è un'interfaccia interna di Map con i metodi getKey() e getValue(). In questo modo ottieni subito entrambe le parti della coppia.

Iterazione tramite Iterator

Iterator<Map.Entry<String, String>> it = users.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, String> entry = it.next();
    // Si può perfino eliminare l'elemento in modo sicuro:
    if (entry.getKey().startsWith("v")) {
        it.remove();
    }
}

6. Esempi reali: come l'iterazione delle collezioni aiuta in un'applicazione

Esempio: stampiamo tutte le attività dell'utente

List<String> tasks = new ArrayList<>();
tasks.add("Fare i compiti");
tasks.add("Accarezzare il gatto");
tasks.add("Guardare una serie");

System.out.println("Le tue attività per oggi:");
for (String task : tasks) {
    System.out.println("- " + task);
}

Ora eliminiamo tutte le attività che contengono la parola "gatto":

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("gatto")) {
        it.remove();
    }
}
System.out.println("Attività rimaste:");
for (String task : tasks) {
    System.out.println("- " + task);
}

Esempio: iterare login univoci con Set

Set<String> logins = new HashSet<>();
logins.add("vasya");
logins.add("petya");
logins.add("masha");

for (String login : logins) {
    System.out.println("Utente: " + login);
}

Attenzione: l'ordine di output in un Set può essere qualsiasi!

Esempio: iterare una Map per visualizzare gli utenti

Map<String, String> users = new HashMap<>();
users.put("vasya", "vasya@example.com");
users.put("petya", "petya@gmail.com");

for (Map.Entry<String, String> entry : users.entrySet()) {
    System.out.println("Login: " + entry.getKey() + ", Email: " + entry.getValue());
}

7. Iterator.remove(): eliminazione sicura degli elementi

Uno degli errori più comuni dei principianti è provare a eliminare elementi di una collezione durante un for-each. L'iterator risolve questo problema con remove().

Come funziona?

  • Quando chiami it.remove(), viene rimosso l'elemento corrente — quello restituito dall'ultimo next().
  • È sicuro: la collezione non lancia ConcurrentModificationException.

Esempio:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
    int n = it.next();
    if (n % 2 == 0) {
        it.remove(); // Rimuoviamo tutti i numeri pari
    }
}
System.out.println(numbers); // [1, 3, 5]

Schema di iterazione di una collezione

+---------+     +---------+     +---------+
| Element | --> | Element | --> | Element | ...
+---------+     +---------+     +---------+
     ^               ^
     |               |
   next()         next()

L'iterator «avanza» sugli elementi finché hasNext() non restituisce false.

9. Errori tipici nell'uso di Iterator e nell'iterazione delle collezioni

Errore n. 1: modificare la collezione durante l'iterazione con for-each.
Tentare di eliminare un elemento direttamente dentro un for-each porta a ConcurrentModificationException:

for (String task : tasks) {
    if (task.contains("gatto")) {
        tasks.remove(task); // BOOM! ConcurrentModificationException
    }
}

Usa Iterator e il suo remove().

Errore n. 2: chiamare remove() prima di next().
Prima devi ottenere l'elemento corrente con next(), altrimenti l'iterator non sa cosa eliminare.

Iterator<String> it = tasks.iterator();
it.remove(); // Errore! Serve prima next()

Errore n. 3: tentare di iterare direttamente una Map in un for-each.
Map non implementa Iterable direttamente — usa keySet(), values() o entrySet().

Map<String, String> users = new HashMap<>();
// for (String entry : users) { ... } // Errore: non si può fare
for (Map.Entry<String, String> e : users.entrySet()) {
    // corretto
}

Errore n. 4: modificare la collezione al di fuori dell'iterator durante un attraversamento con iterator.
Durante l'iterazione elimina gli elementi solo tramite it.remove(), non con i metodi della collezione.

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("gatto")) {
        tasks.remove(task); // Errore! Bisogna usare it.remove()
    }
}
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION