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 |
|---|---|
|
Ci sono altri elementi da scorrere? |
|
Restituisce l'elemento successivo e ci avanza |
|
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()
}
}
GO TO FULL VERSION