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¶
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:
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:
¿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
Ejemplo 3.04: no indicando el tipo de objeto del iterador
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:
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.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().