CodeGym /Cursos /JAVA 25 SELF /Iterable e Iterator: recorrido de colecciones

Iterable e Iterator: recorrido de colecciones

JAVA 25 SELF
Nivel 27, Lección 1
Disponible

1. Interfaz Iterable

En Java casi todas las colecciones (excepto Map) implementan la interfaz Iterable. Esto significa que se pueden recorrer secuencialmente —elemento tras elemento— sin entrar en detalles de la estructura interna. Para el programador, se ve como «la colección tiene una forma integrada de iterar por todos sus elementos».

La interfaz Iterable define exactamente un método:

Iterator<E> iterator();

El método iterator() devuelve un objeto de tipo Iterator, un «ayudante» que sabe cómo recorrer la colección paso a paso. Gracias a esto funciona el conocido bucle for-each:

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

— entre bastidores se esconde ese mismo Iterator. Una ventaja adicional: con su ayuda se pueden eliminar elementos de forma segura durante el recorrido usando remove(). Si se intenta hacerlo con un recorrido normal, es fácil encontrarse con ConcurrentModificationException.

2. Interfaz Iterator

Iterator es un «mensajero» que sabe avanzar por tu colección sin saltarse elementos y respetando su orden de recorrido.

Método Descripción
boolean hasNext()
¿Hay más elementos que recorrer?
E next()
Devuelve el siguiente elemento y avanza a él
void remove()
Elimina el elemento actual de forma segura, sin fallos

Ejemplo de recorrido de una colección con Iterator

import java.util.*;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>();
        tasks.add("Acariciar al gato");
        tasks.add("Hacer la tarea");
        tasks.add("Ver una serie");

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

¿Qué ocurre aquí?

  • Obtenemos el iterador mediante tasks.iterator().
  • Mientras haya un siguiente elemento (hasNext() devuelve true), lo tomamos con next() y lo mostramos.
  • El iterador se encarga del orden de recorrido: no necesitas saber cómo almacena la colección los elementos internamente.

3. ¿Por qué necesitamos Iterator si ya hay bucles?

Con Iterator puedes recorrer cualquier colección, incluso las que no tienen índices (por ejemplo, Set). Es una forma universal que no depende del tipo concreto de colección.

Eliminación segura de elementos

Tarea habitual: recorrer una colección y eliminar algunos elementos. Si lo haces con for-each, puedes obtener un error:

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

¿Por qué sucede? La colección no espera que su estructura se modifique directamente durante un recorrido iniciado por un iterador.

Forma correcta:

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("gato")) {
        it.remove(); // Todo irá bien
    }
}

¿Por qué no se pueden usar índices sin más?

Porque no todas las colecciones tienen índices. Por ejemplo, en HashSet o TreeSet no existe el concepto de «quinto elemento». Iterator funciona siempre — ahí radica su fuerza.

4. Detalles sobre for-each

El bucle for mejorado (for-each) apareció en Java 5. En esencia es “azúcar sintáctico” que permite recorrer elementos de la forma más sencilla:

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

Por debajo, el compilador llama a iterator(), comprueba los elementos con hasNext() y los obtiene con next(). Se lee literalmente como una frase en lenguaje natural: «para cada tarea de la lista».

¿Cuándo no sirve for-each?

  • Hay que eliminar elementos durante el recorrido (for-each no permite invocar remove() directamente).
  • Necesitas acceder al índice para, por ejemplo, sustituir un elemento por posición.
  • Trabajas con Map — tiene pares «clave-valor» y necesita su propia lógica de recorrido.

5. Recorrido de Map: trucos y matices

La interfaz Map no implementa Iterable directamente, ya que es un conjunto de pares «clave-valor». Aun así, Map ofrece vistas cómodas para recorrerla.

Recorrer por claves

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

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

Recorrer por valores

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

Recorrer por pares (clave-valor)

La forma más universal es recorrer por entrySet():

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

Dato interesante: Entry es una interfaz interna de Map con los métodos getKey() y getValue(). Así obtienes ambas partes del par de una vez.

Recorrer con Iterator

Iterator<Map.Entry<String, String>> it = users.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, String> entry = it.next();
    // Incluso se puede eliminar el elemento de forma segura:
    if (entry.getKey().startsWith("v")) {
        it.remove();
    }
}

6. Ejemplos reales: cómo el recorrido de colecciones ayuda en una aplicación

Ejemplo: mostramos todas las tareas del usuario

List<String> tasks = new ArrayList<>();
tasks.add("Hacer la tarea");
tasks.add("Acariciar al gato");
tasks.add("Ver una serie");

System.out.println("Tus tareas para hoy:");
for (String task : tasks) {
    System.out.println("- " + task);
}

Ahora eliminemos todas las tareas que contienen la palabra "gato":

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

Ejemplo: recorrer logins únicos con Set

Set<String> logins = new HashSet<>();
logins.add("john");
logins.add("peter");
logins.add("mary");

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

Atención: ¡el orden de salida en Set puede ser cualquiera!

Ejemplo: recorrer un Map para mostrar usuarios

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

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

7. Iterator.remove(): eliminación segura de elementos

Uno de los errores más comunes de los principiantes es intentar eliminar elementos de una colección durante un recorrido con for-each. El iterador resuelve este problema con remove().

¿Cómo funciona?

  • Al llamar a it.remove() se elimina el elemento actual, es decir, el que devolvió el último next().
  • Es seguro: la colección no lanza ConcurrentModificationException.

Ejemplo:

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(); // Eliminamos todos los números pares
    }
}
System.out.println(numbers); // [1, 3, 5]

Esquema de recorrido de una colección

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

El iterador «avanza» por los elementos hasta que hasNext() devuelve false.

9. Errores típicos al trabajar con Iterator y al recorrer colecciones

Error n.º 1: modificación de la colección durante el recorrido con for-each.
Intentar eliminar un elemento directamente dentro de for-each lleva a ConcurrentModificationException:

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

Usa Iterator y su remove().

Error n.º 2: llamar a remove() antes de next().
Primero hay que obtener el elemento actual con next(), si no, el iterador no sabe qué eliminar.

Iterator<String> it = tasks.iterator();
it.remove(); // Error. Primero hay que llamar a next()

Error n.º 3: intentar recorrer Map directamente en un for-each.
Map no implementa Iterable directamente; usa keySet(), values() o entrySet().

Map<String, String> users = new HashMap<>();
// for (String entry : users) { ... } // Error: esto no se puede hacer así
for (Map.Entry<String, String> e : users.entrySet()) {
    // correcto
}

Error n.º 4: modificar la colección fuera del iterador mientras se recorre con él.
Durante el recorrido, elimina elementos solo con it.remove(), no con los métodos de la colección.

Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
    String task = it.next();
    if (task.contains("gato")) {
        tasks.remove(task); // Error. Debe ser it.remove()
    }
}
1
Tarea
JAVA 25 SELF, nivel 27, lección 1
Bloqueada
Diccionario de idiomas extraterrestres 👽
Diccionario de idiomas extraterrestres 👽
1
Tarea
JAVA 25 SELF, nivel 27, lección 1
Bloqueada
Gestión de la lista de tecnologías para el proyecto 🛠️
Gestión de la lista de tecnologías para el proyecto 🛠️
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION