Guía de Java 8 para cada uno

1. Información general

Introducido en Java 8, el bucle forEach proporciona a los programadores una forma nueva, concisa e interesante de iterar sobre una colección .

En este artículo, veremos cómo usar forEach con colecciones, qué tipo de argumento se necesita y en qué se diferencia este ciclo del ciclo for mejorado .

Si necesita repasar algunos conceptos de Java 8, tenemos una colección de artículos que pueden ayudarlo.

2. Conceptos básicos de forEach

En Java, la interfaz Collection tiene Iterable como su super interfaz, y a partir de Java 8, esta interfaz tiene una nueva API:

void forEach(Consumer action)

En pocas palabras, el Javadoc de las estadísticas de forEach indica que "realiza la acción dada para cada elemento del Iterable hasta que todos los elementos han sido procesados ​​o la acción arroja una excepción".

Y así, con forEach , podemos iterar sobre una colección y realizar una acción determinada en cada elemento, como cualquier otro iterador.

Por ejemplo, una versión de bucle for para iterar e imprimir una colección de cadenas :

for (String name : names) { System.out.println(name); }

Podemos escribir esto usando forEach como:

names.forEach(name -> { System.out.println(name); });

3. Uso del método forEach

Usamos forEach para iterar sobre una colección y realizar una determinada acción en cada elemento. La acción a realizar está contenida en una clase que implementa la interfaz del consumidor y se pasa a forEach como argumento.

La interfaz del consumidor es una interfaz funcional (una interfaz con un único método abstracto). Acepta una entrada y no devuelve ningún resultado.

Aquí está la definición:

@FunctionalInterface public interface Consumer { void accept(T t); }

Por lo tanto, cualquier implementación, por ejemplo, un consumidor que simplemente imprime un String :

Consumer printConsumer = new Consumer() { public void accept(String name) { System.out.println(name); }; };

se puede pasar a forEach como argumento:

names.forEach(printConsumer);

Pero esa no es la única forma de crear una acción a través de un consumidor y usar forEach API.

Veamos las 3 formas más populares en las que usaremos el método forEach :

3.1. Implementación de consumidor anónimo

Podemos instanciar una implementación de la interfaz del consumidor usando una clase anónima y luego aplicarla como un argumento al método forEach :

Consumer printConsumer= new Consumer() { public void accept(String name) { System.out.println(name); } }; names.forEach(printConsumer);

Esto funciona bien, pero si analizamos el ejemplo anterior, veremos que la parte real que es útil es el código dentro del método accept () .

Aunque las expresiones Lambda son ahora la norma y la forma más fácil de hacer esto, todavía vale la pena saber cómo implementar la interfaz del consumidor .

3.2. Una expresión lambda

El principal beneficio de las interfaces funcionales de Java 8 es que podemos usar expresiones Lambda para instanciarlas y evitar el uso de implementaciones de clases anónimas voluminosas.

Como Consumer Interface es una interfaz funcional, podemos expresarla en Lambda en forma de:

(argument) -> { //body }

Por lo tanto, nuestro printConsumer se simplifica a:

name -> System.out.println(name)

Y podemos pasarlo a forEach como:

names.forEach(name -> System.out.println(name));

Desde la introducción de las expresiones Lambda en Java 8, esta es probablemente la forma más común de utilizar el método forEach .

Las lambdas tienen una curva de aprendizaje muy real, por lo que si está comenzando, este artículo repasa algunas buenas prácticas para trabajar con la nueva función de lenguaje.

3.3. Una referencia de método

Podemos usar la sintaxis de referencia del método en lugar de la sintaxis normal de Lambda donde ya existe un método para realizar una operación en la clase:

names.forEach(System.out::println);

4. Trabajar con forEach

4.1. Iterando sobre una colección

Cualquier iterable de tipo Colección: lista, conjunto, cola, etc.tiene la misma sintaxis para usar forEach.

Por tanto, como ya hemos visto, para iterar elementos de una lista:

List names = Arrays.asList("Larry", "Steve", "James"); names.forEach(System.out::println);

De manera similar para un conjunto:

Set uniqueNames = new HashSet(Arrays.asList("Larry", "Steve", "James")); uniqueNames.forEach(System.out::println);

O digamos para una cola que también es una colección :

Queue namesQueue = new ArrayDeque(Arrays.asList("Larry", "Steve", "James")); namesQueue.forEach(System.out::println);

4.2. Iterando sobre un mapa - Usando Map's forEach

Los mapas no son iterables , pero proporcionan su propia variante de forEach que acepta un BiConsumer .

Se introdujo un BiConsumer en lugar de Consumer en Iterable's forEach para que se pueda realizar una acción tanto en la clave como en el valor de un Mapa simultáneamente.

Creemos un mapa con entradas:

Map namesMap = new HashMap(); namesMap.put(1, "Larry"); namesMap.put(2, "Steve"); namesMap.put(3, "James");

A continuación, iteremos sobre namesMap usando Map's forEach :

namesMap.forEach((key, value) -> System.out.println(key + " " + value));

Como podemos ver aquí, hemos utilizado un BiConsumer :

(key, value) -> System.out.println(key + " " + value)

para iterar sobre las entradas del Mapa .

4.3. Iteración en un Mapa - iterando entrySet

También podemos iterar el EntrySet de un mapa usando Iterable's forEach.

Dado que las entradas de un mapa se almacenan en un conjunto llamado EntrySet, podemos iterar eso usando un forEach:

namesMap.entrySet().forEach(entry -> System.out.println( entry.getKey() + " " + entry.getValue()));

5. Foreach vs For-Loop

Desde un punto de vista simple, ambos bucles proporcionan la misma funcionalidad: recorrer los elementos de una colección.

La principal diferencia entre los dos es que son iteradores diferentes: el bucle for mejorado es un iterador externo, mientras que el nuevo método forEach es interno .

5.1. Iterador interno: para cada uno

Este tipo de iterador gestiona la iteración en segundo plano y deja que el programador solo codifique lo que se debe hacer con los elementos de la colección.

En cambio, el iterador gestiona la iteración y se asegura de procesar los elementos uno por uno.

Veamos un ejemplo de un iterador interno:

names.forEach(name -> System.out.println(name));

En el método forEach anterior, podemos ver que el argumento proporcionado es una expresión lambda. Esto significa que el método solo necesita saber qué se debe hacer y todo el trabajo de iteración se realizará internamente.

5.2. Iterador externo - for-loop

Los iteradores externos mezclan qué y cómo se va a realizar el ciclo.

Las enumeraciones , los iteradores y el bucle for mejorado son todos iteradores externos (recuerde los métodos iterator (), next () o hasNext () ?). En todos estos iteradores es nuestro trabajo especificar cómo realizar las iteraciones.

Considere este ciclo familiar:

for (String name : names) { System.out.println(name); }

Aunque no estamos invocando explícitamente los métodos hasNext () o next () mientras iteramos sobre la lista, el código subyacente que hace que esta iteración funcione usa estos métodos. Esto implica que la complejidad de estas operaciones está oculta al programador pero aún existe.

A diferencia de un iterador interno en el que la colección hace la iteración en sí misma, aquí requerimos un código externo que saque todos los elementos de la colección.

6. Conclusión

En este artículo, mostramos que el bucle forEach es más conveniente que el bucle for normal .

También vimos cómo funciona el método forEach y qué tipo de implementación se puede recibir como argumento para realizar una acción en cada elemento de la colección.

Finalmente, todos los fragmentos utilizados en este artículo están disponibles en nuestro repositorio de Github.