Guida a Java 8 forEach

1. Panoramica

Introdotto in Java 8, il ciclo forEach fornisce ai programmatori un modo nuovo, conciso e interessante per l'iterazione su una raccolta .

In questo articolo vedremo come usare forEach con le raccolte, che tipo di argomento richiede e come questo ciclo differisce dal ciclo for migliorato .

Se hai bisogno di rispolverare alcuni concetti di Java 8, abbiamo una raccolta di articoli che possono aiutarti.

2. Nozioni di base su forEach

In Java, l' interfaccia Collection ha Iterable come super interfaccia e, a partire da Java 8, questa interfaccia ha una nuova API:

void forEach(Consumer action)

In poche parole, il Javadoc di forEach dichiara che "esegue l'azione data per ogni elemento di Iterable finché tutti gli elementi non sono stati elaborati o l'azione genera un'eccezione".

E così, con forEach , possiamo iterare su una raccolta ed eseguire una determinata azione su ogni elemento, come qualsiasi altro Iteratore.

Ad esempio, una versione for-loop di iterazione e stampa di una raccolta di stringhe :

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

Possiamo scrivere questo usando forEach come:

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

3. Utilizzo del metodo forEach

Usiamo forEach per iterare su una raccolta ed eseguire una determinata azione su ogni elemento. L'azione da eseguire è contenuta in una classe che implementa l' interfaccia Consumer e viene passata a forEach come argomento.

L' interfaccia Consumer è un'interfaccia funzionale (un'interfaccia con un unico metodo astratto). Accetta un input e non restituisce alcun risultato.

Ecco la definizione:

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

Pertanto, qualsiasi implementazione, ad esempio, un consumatore che stampa semplicemente una stringa :

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

può essere passato a forEach come argomento:

names.forEach(printConsumer);

Ma questo non è l'unico modo per creare un'azione tramite un consumatore e utilizzare forEach API.

Vediamo i 3 modi più popolari in cui useremo il metodo forEach :

3.1. Implementazione anonima del consumatore

Possiamo istanziare un'implementazione dell'interfaccia Consumer utilizzando una classe anonima e quindi applicarla come argomento al metodo forEach :

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

Funziona bene, ma se analizziamo l'esempio sopra vedremo che la parte effettiva che è utile è il codice all'interno del metodo accept () .

Sebbene le espressioni Lambda siano ora la norma e il modo più semplice per farlo, vale comunque la pena sapere come implementare l' interfaccia Consumer .

3.2. Un'espressione Lambda

Il vantaggio principale delle interfacce funzionali Java 8 è che possiamo utilizzare espressioni Lambda per istanziarle ed evitare di utilizzare ingombranti implementazioni di classi anonime.

Poiché Consumer Interface è un'interfaccia funzionale, possiamo esprimerla in Lambda sotto forma di:

(argument) -> { //body }

Pertanto, il nostro printConsumer si semplifica per:

name -> System.out.println(name)

E possiamo passarlo a forEach come:

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

Dall'introduzione delle espressioni Lambda in Java 8, questo è probabilmente il modo più comune per utilizzare il metodo forEach .

Lambda ha una curva di apprendimento molto reale, quindi se stai iniziando, questo articolo ripercorre alcune buone pratiche per lavorare con la nuova funzionalità del linguaggio.

3.3. Un metodo di riferimento

Possiamo utilizzare la sintassi di riferimento del metodo invece della normale sintassi Lambda in cui esiste già un metodo per eseguire un'operazione sulla classe:

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

4. Lavorare con forEach

4.1. Iterazione su una raccolta

Qualsiasi iterabile di tipo Collection - list, set, queue ecc . Ha la stessa sintassi per l'utilizzo di forEach.

Pertanto, come abbiamo già visto, per iterare elementi di una lista:

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

Allo stesso modo per un set:

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

O diciamo per una coda che è anche una raccolta :

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

4.2. Iterazione su una mappa - Utilizzo di forEach di Map

Le mappe non sono modificabili , ma forniscono la propria variante di forEach che accetta un BiConsumer .

Un BiConsumer è stato introdotto al posto di Consumer in Iterable's forEach in modo che un'azione possa essere eseguita contemporaneamente sulla chiave e sul valore di una mappa .

Creiamo una mappa con voci:

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

Successivamente, iteriamo su namesMap usando Map's forEach :

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

Come possiamo vedere qui, abbiamo utilizzato un BiConsumer :

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

per scorrere le voci della mappa .

4.3. L'iterazione di una mappa - scorrendo entrySet

Possiamo anche iterare EntrySet di una mappa usando forEach di Iterable.

Poiché le voci di una mappa sono memorizzate in un Set chiamato EntrySet, possiamo iterarlo usando un forEach:

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

5. Foreach vs For-Loop

Da un semplice punto di vista, entrambi i cicli forniscono la stessa funzionalità: consentono di scorrere gli elementi in una raccolta.

La differenza principale tra i due è che sono iteratori diversi: il ciclo for migliorato è un iteratore esterno mentre il nuovo metodo forEach è interno .

5.1. Iteratore interno - forEach

Questo tipo di iteratore gestisce l'iterazione in background e lascia al programmatore il solo codice di ciò che si intende fare con gli elementi della raccolta.

L'iteratore invece, gestisce l'iterazione e si assicura di elaborare gli elementi uno per uno.

Vediamo un esempio di un iteratore interno:

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

Nel metodo forEach sopra, possiamo vedere che l'argomento fornito è un'espressione lambda. Ciò significa che il metodo deve solo sapere cosa deve essere fatto e tutto il lavoro di iterazione sarà curato internamente.

5.2. Iteratore esterno - ciclo for

Gli iteratori esterni mescolano cosa e come deve essere fatto il ciclo.

Enumerazioni , iteratori e ciclo for migliorato sono tutti iteratori esterni (ricordate i metodi iterator (), next () o hasNext () ?). In tutti questi iteratori è nostro compito specificare come eseguire le iterazioni.

Considera questo ciclo familiare:

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

Sebbene non stiamo invocando esplicitamente i metodi hasNext () o next () durante l'iterazione della lista, il codice sottostante che fa funzionare questa iterazione utilizza questi metodi. Ciò implica che la complessità di queste operazioni è nascosta al programmatore ma esiste ancora.

Contrariamente a un iteratore interno in cui la raccolta esegue l'iterazione stessa, qui abbiamo bisogno di codice esterno che tolga ogni elemento dalla raccolta.

6. Conclusione

In questo articolo, abbiamo mostrato che il ciclo forEach è più conveniente del normale ciclo for .

Abbiamo anche visto come funziona il metodo forEach e quale tipo di implementazione può ricevere come argomento per eseguire un'azione su ogni elemento della raccolta.

Infine, tutti gli snippet utilizzati in questo articolo sono disponibili nel nostro repository Github.