Java – Il metodo forEach di Map da Java 8

In Java 8 sono stati introdotti nella interfaccia java.util.Map svariati nuovi metodi, tra cui un nuovo metodo chiamato forEach. In questo articolo descriverò questo metodo usando anche un esempio pratico.

Il nuovo metodo forEach di Map innanzitutto ha la seguente forma:

default void forEach(BiConsumer<? super K,? super V> action)

Il forEach è quindi un metodo di “default”, una nuova funzionalità di Java 8 che permette di definire in una interface Java un metodo che possiede una implementazione, cioè ha un corpo con del codice e quindi non è “astratto”.

L’unico parametro del forEach è un BiConsumer. Questo BiConsumer è un’altra novità di Java 8, si tratta di una functional interface introdotta insieme a molte altre all’interno del nuovo package java.util.function. Dalla denominazione di questa interfaccia si possono dedurre due cose:

  • il nome “Consumer” denota che una implementazione di BiConsumer serve per “consumare”, cioè ricevere, delle informazioni
  • il prefisso “Bi” denota che BiConsumer è in grado di ricevere esattamente 2 dati

Il funzionamento del forEach quindi è molto semplice: ricevendo in argomento una implementazione di BiConsumer, esegue una iterazione sulla mappa per estrarre tutte le entry e per ciascuna invoca il metodo accept del BiConsumer passando come argomenti la chiave e il valore della entry. La implementazione di BiConsumer può implementare il accept per fare sostanzialmente quello che vuole con la chiave e il valore, il caso più tipico è stampare questi dati in qualche modo.

BiConsumer, essendo una interfaccia, si può implementare in vari modi: con una normale classe, con una nested class, con una inner class (anche, tipicamente, di tipo anonymous) oppure con le lambda expression disponibili da Java 8.

Il seguente è un esempio molto basilare dell’uso di forEach sfruttando una lambda expression.

import java.util.HashMap;
import java.util.Map;

public class ProvaForEachMap {
    public static void main(String[] args) {
        Map<String, Integer> mappaVoti = new HashMap<>();
        mappaVoti.put("Italiano", 6);
        mappaVoti.put("Matematica", 9);
        mappaVoti.put("Geografia", 7);
        mappaVoti.put("Chimica", 5);

        mappaVoti.forEach((materia, voto) -> System.out.format(
                "%-10s , voto: %d%n", materia, voto));
    }
}

L’output è ad esempio il seguente:

Geografia  , voto: 7
Chimica    , voto: 5
Matematica , voto: 9
Italiano   , voto: 6

Da notare come la stampa delle materie non sia ordinata né per ordine di inserimento, né in base al contenuto delle stringhe. Questo perché è stata usata la implementazione HashMap che per sua definizione è unsorted/unordered, ovvero non c’è un ordine delle chiavi che sia (facilmente) predicibile. Se fosse stato usato un TreeMap (implementazione sorted di Map) la stampa sarebbe sempre stata esattamente questa:

Chimica    , voto: 5
Geografia  , voto: 7
Italiano   , voto: 6
Matematica , voto: 9

Volendo implementare il BiConsumer con una anonymous inner class invece che con una lambda expression, si poteva fare nel seguente modo:

        mappaVoti.forEach(new BiConsumer<String, Integer>() {
            @Override
            public void accept(String materia, Integer voto) {
                System.out.format("%-10s , voto: %d%n", materia, voto);
            }
        });