Recorrer una lista es una operación básica de una colección, pero con el tiempo esta ha tenido cambios significativos. Empezaremos con la forma antigua y evolucionaremos esta mediante ejemplos a un estilo más elegante.
Se puede crear una lista de elementos inmutables con el siguiente código:
1 | final List<String> friends = Arrays.asList(“Brian”, “Nate”, “Neal”, “Raju”, “Sara”, “Scott”); |
A continuación una forma para recorrer e imprimir cada uno de los elementos:
1 2 3 | for(int i = 0; i < friends.size(); i++) { System.out.println(friends.get(i)); } |
El código presentado es verboso y propenso a errores, sentencias tales como “i < o i < = “son propensas a generar errores. Esto es útil si queremos manipular determinados en una posición particular, incluso así, podemos utilizar la programación funcional.
Incluso para este bucle, Java presenta una opción más civilizada:
1 2 3 | for(String name : friends) { System.out.println(name); } |
Al utilizar esta forma de recorrer el listado se utiliza la interface Iterator e internamente se llama a los métodos hasNext y next.
Ambas versiones son iteraciones externas, que mezclan como debemos realizar el recorrido con aquello que queremos lograr. Se controla la iteración en forma explícita, indicando cuando iniciar y cuando terminar. La segunda versión es mejor si no queremos modificar la colección en una posición en particular. Ambas versiones sin embargo son del tipo imperativo.
La interface Iterable ha sido mejorada en JDK 8 con el método forEach, que acepta como parámetro el tipo Consumer. Cómo lo indica el nombre, una instancia de Consumer será utilizada a través del método accept. Vamos a utilizar este método:
1 2 3 4 5 | friends.forEach(new Consumer<String>() { public void accept(final String name) { System.out.println(name); } }); |
Invocamos forEach en la colección friends y enviamos una instancia anónima de Consumer a través de él. El método forEach invoca el método accept para cada Consumer y realiza aquello que queremos realizar. Este ejemplo es sencillo e imprime el contenido del listado.
Este cambio del for externo por un forEach interno, ayuda a tener un foco en lo que se debe hacer en cada iteración, en lugar de tener el foco en controlar la iteración. La desventaja es que el código es más verboso. Felizmente, esto puede mejorar con las expresiones Lambda. Pasemos a reemplazar la clase anónima por una expresiones Lambda.
1 | friends.forEach((final String name) -> System.out.println(name)); |
Esto se ve mucho mejor y esta versión produce el mismo resultado que la versión anterior. Sin embargo, esta versión tiene una limitación, que una vez iniciada la iteración, no podemos utilizar la sentencia break en la iteración. Como consecuencia, esta opción es útil en casos donde se debe recorrer todo el listado.
El compilador de Java también puede inferir el tipo de dato que se está utilizando. El código quedaría de la siguiente forma:
1 | friends.forEach((name) -> System.out.println(name)); |
Existe una consideración: los parámetros inferidos no son del tipo final. En el ejemplo previo, cuando indicamos el tipo, se indicó que el tipo era final. Esto previene a que el parámetro sea modificado con la expresion Lambda. En general, marcar la variable como final es una buena práctica. Desafortunadamente, si utilizamos la opción de que la máquina virtual infiera el dato, se debe tener cuidado de no modificar el valor.
De esta forma las expresiones Lambda nos ayuda a recorrer una lista en forma sencilla.