Skip to content

7.3 Iteradores

¿Qué son los iteradores realmente? Son un mecanismo que nos permite recorrer todos los elementos de una colección de forma sencilla, de forma secuencial, y de forma segura.

Mapas

Los mapas, como no derivan de la interfaz Collection realmente, no tienen iteradores, pero como veremos, existe un truco interesante.

Los iteradores permiten recorrer las colecciones de dos formas:

  • a través de bucles for‐each (existentes en Java a partir de la versión 1.5) y
  • a través de un bucle normal creando un iterador.

Como los bucles for-each ya los hemos visto antes (y ha quedado patente su simplicidad), nos vamos a centrar en el otro método, especialmente útil en versiones antiguas de Java. Ahora la pregunta es, ¿cómo se crea un iterador? Pues invocando el método iterator() de cualquier colección.

3.1. Creación de iterador

Iterator<E> it = coleccion.iterator();

Donde que coleccion es una clase genérica de tipo Collection y en donde <E> es el parámetro de tipo (podría ser cualquier clase).

Ejemplo 3.01: iterator

En este ejemplo c es una colección cualquiera:

Iterator<Integer> it = c.iterator();

Fíjate que se ha especificado un parámetro para el tipo de dato genérico en el iterador (poniendo <Integer> después de Iterator). Esto es porque los iteradores son también clases genéricas, y es necesario especificar el tipo base que contendrá el iterador.

Si no se especifica el tipo base del iterador, igualmente nos permitiría recorrer la colección, pero retornará objetos tipo Object (clase de la que derivan todas las clases), con lo que nos veremos obligados a forzar la conversión de tipo.

3.2. Recorrido y gestión de un iterador

método descripción
boolean hasNext() retornará true si le quedan más elementos a la colección por visitar, false en caso contrario.
E next() retornará el siguiente elemento de la colección, si no existe siguiente elemento, lanzará una excepción ( NoSuchElementException para ser exactos), con lo que conviene chequear primero si el siguiente elemento existe.
remove() elimina de la colección el último elemento retornado en la última invocación de next (no es necesario pasárselo por parámetro). Cuidado, si next no ha sido invocado todavía, saltará una incómoda excepción.

¿Cómo recorreríamos una colección con estos métodos? Pues de una forma muy sencilla, un simple bucle while con la condición hasNext() nos permite hacerlo:

Ejemplo 3.02: iterator

En este ejemplo se recorre un iterator y se eliminan de la lista los número pares:

1
2
3
4
5
6
while (it.hasNext()) {    // mientras haya siguiente elemento, seguiremos en el bucle
   Integer n = it.next(); // escogemos el siguiente elemento

   if (n%2 == 0)
      it.remove();        // si es par, eliminamos el elemento de la lista
}
¿Qué elementos contendría la lista después de ejecutar el bucle? Efectivamente, solo números impares.

Acceso posicional en las listas con métodos get y set

Las listas permiten acceso posicional a través de los métodos get y set, y acceso secuencial a través de iteradores, ¿cuál es para tí la forma más cómoda de recorrer todos los elementos, un acceso posicional a través un bucle for (i=0; i<lista.size(); i++) o un acceso secuencial usando un bucle while (iterador.hasNext())?

¿Qué inconvenientes tiene usar los iteradores sin especificar el tipo de objeto? En los dos siguientes ejemplos, se genera una lista con los números del 0 al 10. De la lista, se eliminan aquellos que son pares y solo se dejan los impares. En el primer ejemplo se especifica el tipo de objeto del iterador, en el segundo ejemplo no; observa el uso de la conversión de tipos en la línea 7 de este último.

Ejemplo 3.03: indicando el tipo de objeto del iterador
ArrayList<Integer> lista = new ArrayList<Integer>();


for (int i=0; i<10; i++){
    lista.add(i); 
} 
//lista: [0,1,2,3,4,5,6,7,8,9]

Iterator<Integer> it = lista.iterator();

while (it.hasNext()) {
    Integer n = it.next();
    if (n%2 == 0){
        it.remove();
    }
}
//lista: [1,3,5,7,9]
Ejemplo 3.04: no indicando el tipo de objeto del iterador
ArrayList <Integer> lista = new ArrayList<Integer>();

for (int i=0; i<10; i++){
    lista.add(i); 
}

Iterator it = lista.iterator();

while (it.hasNext()) {
    Integer n = (Integer)it.next();
    if (n%2 == 0){
        it.remove();
    }
}

Un iterador es seguro porque está pensado para no sobrepasar los límites de la colección, ocultando operaciones más complicadas que pueden repercutir en errores de software. Pero realmente se convierte en inseguro cuando es necesario hacer la operación de conversión de tipos. Si la colección no contiene los objetos esperados, al intentar hacer la conversión, saltará una incómoda excepción.

Usar genéricos aporta grandes ventajas, pero usándolos adecuadamente.

3.3. Recorrer Mapas con iteradores

Para recorrer los mapas con iteradores, hay que hacer un pequeño truco. Usamos el método entrySet que ofrecen los mapas para generar un conjunto con las entradas (pares de clave‐valor), o bien, el método keySet para generar un conjunto con las claves existentes en el mapa.

Ejemplo 3.05: con método keySet(), el más sencillo:

1
2
3
4
5
6
7
8
9
Map<String, Integer> mapa = new HashMap<>();
mapa.put("A", 100);
mapa.put("B", 200);
mapa.put("C", 300);

// Recorrer con for-each usando keySet()
for (String clave : mapa.keySet()) {
    System.out.println("Clave: " + clave + ", Valor: " + mapa.get(clave));
}
Lo único que tienes que tener en cuenta es que el conjunto generado por keySet no tendrá obviamente el método add para añadir elementos al mismo, dado que eso tendrás que hacerlo a través del mapa.

Ejemplo 3.06: con método entrySet():

Map<String, Integer> mapa = new HashMap<>();
mapa.put("Uno", 1);
mapa.put("Dos", 2);
mapa.put("Tres", 3);


// Recorrer con for-each usando entrySet()
for (Map.Entry<String, Integer> elemento : mapa.entrySet()) {
    System.out.println("Clave: " + elemento.getKey() + ", Valor: " + elemento.getValue());
}
Esta interfaz estática Map.Entry<K, V> permite trabajar con pares clave-valor dentro de un Map. Map.Entry<K,V> no depende de ninguna instancia de Map y se puede utilizar sin necesidad de crear un objeto Map. Se usa principalmente cuando se quiere recorrer un Map con un bucle for-each en conjunto con entrySet().

Eliminar elementos de una colección con iteradores

Si usas iteradores, y piensas eliminar elementos de la colección (e incluso de un mapa), debes usar el método remove del iterador y no el de la colección. Si eliminas los elementos utilizando el método remove de la colección, mientras estás dentro de un bucle de iteración, o dentro de un bucle for‐each, los fallos que pueden producirse en tu programa son impredecibles. ¿Logras adivinar porqué se pueden producir dichos problemas?

Los problemas son debidos a que el método remove del iterador elimina el elemento de dos sitios: de la colección y del iterador en sí (que mantiene interiormente información del orden de los elementos). Si usas el método remove de la colección, la información solo se elimina de un lugar, de la colección.

Ejemplo 3.07: formas para recorrer ArrayList

Ejemplo que crea, rellena y recorre un ArrayList de dos formas diferentes (con un for y con un iterador).

Cabe destacar que, por defecto, el método System.out.println() invoca al método toString() de los elementos que se le pasen como argumento, por lo que realmente no es necesario utilizar toString() dentro de println().

package UT07.P3.Iterators;

import java.util.ArrayList;
import java.util.Iterator;

public class EjemploRecorrerArrayList {

  public static void main(String[] args) {
    // creamos la lista
    ArrayList l = new ArrayList();

    // añadimos elementos al final de la lista
    l.add("uno");
    l.add("dos");
    l.add("tres");
    l.add("cuatro");

    // añadimos el elemento en la posición 2
    l.add(2, "dos2");

    System.out.println(l.size()); //devuelve 5
    System.out.println(l.get(0)); //devuelve uno
    System.out.println(l.get(1)); //devuelve dos
    System.out.println(l.get(2)); //devuelve dos2
    System.out.println(l.get(3)); //devuelve tres
    System.out.println(l.get(4)); //devuelve cuatro

    // 1) for: recorremos la lista con un for y mostramos su contenido
    for (int i = 0; i < l.size(); i++) {
       System.out.print(l.get(i));
    } //imprime: unodosdos2trescuatro

    System.out.println();

    // 2) iterator: recorremos la lista con un Iterador
    // creamos el iterador
    Iterator<String> it = l.iterator();

    // mientras haya elementos
    while (it.hasNext()) {
       System.out.print(it.next()); //obtengo el siguiente elemento
    } //imprime; unodosdos2trescuatro

    System.out.println();

    for (String elemento : l) {
       System.out.print(elemento);
    } //imprime: unodosdos2trescuatro
  }
}