Guide de Java 8 forEach

1. Vue d'ensemble

Introduite dans Java 8, la boucle forEach fournit aux programmeurs une nouvelle façon concise et intéressante d'itérer sur une collection .

Dans cet article, nous verrons comment utiliser forEach avec des collections, quel type d'argument il prend et en quoi cette boucle diffère de la boucle for améliorée .

Si vous avez besoin de rafraîchir certains concepts de Java 8, nous avons une collection d'articles qui peuvent vous aider.

2. Principes de base de forEach

En Java, l' interface Collection a Iterable comme super-interface - et à partir de Java 8, cette interface a une nouvelle API:

void forEach(Consumer action)

En termes simples, le Javadoc de forEach indique qu'il «exécute l'action donnée pour chaque élément de l'itérable jusqu'à ce que tous les éléments aient été traités ou que l'action lève une exception.

Et donc, avec forEach , nous pouvons parcourir une collection et effectuer une action donnée sur chaque élément, comme n'importe quel autre Iterator.

Par exemple, une version en boucle for de l'itération et de l'impression d'une collection de chaînes :

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

Nous pouvons écrire ceci en utilisant forEach comme:

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

3. Utilisation de la méthode forEach

Nous utilisons forEach pour parcourir une collection et effectuer une certaine action sur chaque élément. L'action à effectuer est contenue dans une classe qui implémente l' interface Consumer et est passée à forEach en tant qu'argument.

L' interface Consumer est une interface fonctionnelle (une interface avec une seule méthode abstraite). Il accepte une entrée et ne renvoie aucun résultat.

Voici la définition:

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

Par conséquent, toute implémentation, par exemple, un consommateur qui imprime simplement une chaîne :

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

peut être passé à forEach comme argument:

names.forEach(printConsumer);

Mais ce n'est pas le seul moyen de créer une action via un consommateur et d'utiliser l' API forEach .

Voyons les 3 façons les plus courantes d'utiliser la méthode forEach :

3.1. Implémentation de consommateur anonyme

Nous pouvons instancier une implémentation de l' interface Consumer à l' aide d'une classe anonyme, puis l'appliquer en tant qu'argument à la méthode forEach :

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

Cela fonctionne bien mais si nous analysons l'exemple ci-dessus, nous verrons que la partie réelle qui est utilisée est le code à l'intérieur de la méthode accept () .

Bien que les expressions Lambda soient désormais la norme et le moyen le plus simple de le faire, il est toujours utile de savoir comment implémenter l' interface Consumer .

3.2. Une expression lambda

Le principal avantage des interfaces fonctionnelles Java 8 est que nous pouvons utiliser des expressions Lambda pour les instancier et éviter d'utiliser des implémentations de classes anonymes volumineuses.

Comme Consumer Interface est une interface fonctionnelle, nous pouvons l'exprimer dans Lambda sous la forme de:

(argument) -> { //body }

Par conséquent, notre printConsumer se simplifie en:

name -> System.out.println(name)

Et nous pouvons le transmettre à forEach comme:

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

Depuis l'introduction des expressions Lambda dans Java 8, c'est probablement la manière la plus courante d'utiliser la méthode forEach .

Les lambdas ont une courbe d'apprentissage très réelle, donc si vous commencez, cet article passe en revue quelques bonnes pratiques d'utilisation de la nouvelle fonctionnalité linguistique.

3.3. Une référence de méthode

Nous pouvons utiliser la syntaxe de référence de méthode au lieu de la syntaxe Lambda normale où une méthode existe déjà pour effectuer une opération sur la classe:

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

4. Travailler avec forEach

4.1. Itérer sur une collection

Tout itérable de type Collection - liste, ensemble, file d'attente, etc. ont la même syntaxe pour utiliser forEach.

Par conséquent, comme nous l'avons déjà vu, pour itérer des éléments d'une liste:

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

De même pour un ensemble:

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

Ou disons pour une file d'attente qui est aussi une collection :

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

4.2. Itérer sur une carte - Utilisation de forEach de la carte

Les cartes ne sont pas itérables , mais elles fournissent leur propre variante de forEach qui accepte un BiConsumer .

Un BiConsumer a été introduit à la place de Consumer dans forEach d'Iterable afin qu'une action puisse être effectuée simultanément sur la clé et la valeur d'une carte .

Créons une carte avec des entrées:

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

Ensuite, nous allons itérer sur namesMap à l' aide de carte forEach :

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

Comme nous pouvons le voir ici, nous avons utilisé un BiConsumer :

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

pour parcourir les entrées de la carte .

4.3. Itération sur une carte - en itérant une entrée

Nous pouvons également itérer le EntrySet d'une carte en utilisant forEach d'Iterable .

Puisque les entrées d'une carte sont stockées dans un ensemble appelé EntrySet, nous pouvons itérer cela en utilisant un forEach:

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

5. Foreach vs For-Loop

D'un point de vue simple, les deux boucles fournissent la même fonctionnalité - une boucle à travers les éléments d'une collection.

La principale différence entre les deux est qu'il s'agit d'itérateurs différents - la boucle for améliorée est un itérateur externe alors que la nouvelle méthode forEach est une méthode interne .

5.1. Itérateur interne - forEach

Ce type d'itérateur gère l'itération en arrière-plan et laisse au programmeur le soin de simplement coder ce qui est censé être fait avec les éléments de la collection.

L'itérateur à la place gère l'itération et s'assure de traiter les éléments un par un.

Voyons un exemple d'itérateur interne:

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

Dans la méthode forEach ci-dessus, nous pouvons voir que l'argument fourni est une expression lambda. Cela signifie que la méthode n'a besoin que de savoir ce qui doit être fait et que tout le travail d'itération sera pris en charge en interne.

5.2. Itérateur externe - boucle for

Les itérateurs externes mélangent le quoi et le comment la boucle doit être faite.

Les énumérations , les itérateurs et les boucles for améliorées sont tous des itérateurs externes (rappelez-vous les méthodes iterator (), next () ou hasNext () ?). Dans tous ces itérateurs, c'est notre travail de spécifier comment effectuer les itérations.

Considérez cette boucle familière:

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

Bien que nous n'invoquions pas explicitement les méthodes hasNext () ou next () lors de l'itération sur la liste, le code sous-jacent qui fait fonctionner cette itération utilise ces méthodes. Cela implique que la complexité de ces opérations est cachée au programmeur mais qu'elle existe toujours.

Contrairement à un itérateur interne dans lequel la collection effectue l'itération elle-même, nous avons ici besoin d'un code externe qui retire chaque élément de la collection.

6. Conclusion

Dans cet article, nous avons montré que la boucle forEach est plus pratique que la boucle for normale .

Nous avons également vu comment fonctionne la méthode forEach et quel type d'implémentation peut recevoir en argument pour effectuer une action sur chaque élément de la collection.

Enfin, tous les extraits utilisés dans cet article sont disponibles dans notre référentiel Github.