Dans toutes applications, surtout en Big Data, on sera toujours amené à effectuer des traitements sur un ensemble de données, que ce soit dans une base de données ou dans d’autres structures de données. Et pour que ce traitement puisse se faire, il va nous falloir une méthode d’itération qui permettra de modifier ou de vérifier chacune des valeurs contenues dans la structure. C’est justement pour cela qu’en Java, et dans plusieurs autres langages, les boucles telles que la boucle for-each et la méthode forEach ont été créé.
En Java, il existe plusieurs façons d’itérer sur des collections, des tableaux ou des Maps. En Java 5, la boucle for améliorée ou for-each (for(String s : collection)) a été introduite afin d’éliminer le désordre associé aux structures de boucles traditionnelles.
Cette boucle for-each n’est pas à confondre avec la méthode forEach() introduite à partir du Java 8 sur lequel nous discuterons aussi dans cet article.
La boucle for-each en Java alias boucle for améliorée
Avant d’entrer dans le vif du sujet, il faut savoir que les boucles sont utilisées afin de manipuler des structures et des collections de données. Donc pour bien utiliser la bonne boucle pour chaque type de structure ou de collection, il faut tout d’abord les comprendre. Et pour cela, vous pouvez vous référer à deux articles déjà présents. Sur ces articles, on vous apprend à maitriser les collections et structures de données de Java mais également à manipuler les ArrayList en Java.
La boucle for-each a été ajoutée dans Java à partir de la version 5. Elle permet aux développeurs d’itérer sur des collections, tout comme le faisaient la boucle for dans les versions précédentes de Java. Elle est aussi parfois connue sous le nom de boucle for améliorée.
Ici, nous allons voir comment utiliser for-each au sein d’un programme mais également pourquoi opter pour cette boucle au lieu de maintenir l’utilisation de ces prédécesseurs.
Utilisation de la boucle for-each en Java
Pour utiliser la boucle for-each et en profiter pour ces prochaines applications Big Data, il faut savoir bien l’utiliser. Et rien de tel qu’un exemple pour illustrer la force de cette boucle dans un programme.
Dans le code ci-dessous, supposons que nous ayons une collection de couleurs sous forme de chaînes (par exemple, List<String> couleurs). Nous pouvons itérer sur la collection de couleurs en utilisant for-each pour afficher chaque couleur :
public static void main(String[] args){
List couleurs = new ArrayList();
couleurs.add("Vert");
couleurs.add("Bleu");
couleurs.add("Orange");
// for-each loop (boucle for améliorée)
for (String couleur : couleurs) {
System.out.println(couleur);
}
}
Ce petit programme que vous venez de voir, la boucle for-each est utiliser afin de prendre une par une les valeurs de la liste et le passe chacun à son tour dans la variable couleur avant de l’afficher en se servant de cette variable.
Le code ci-dessus produit l’affichage suivant :
Vert
Bleu
Orange
Il faut noter que la boucle ci-dessus se lit comme suit : « Pour chaque couleur de la liste des couleurs «
La boucle for-each en Java vs ces prédécesseurs
Avant la boucle for-each, les développeurs parcouraient des collections à l’aide des structures de boucles traditionnelles for, while ou do-while.
Pourquoi alors fournir un autre moyen de parcourir une collection ? Il s’avère qu’il y avait une bonne raison.
L’itération sur une collection à l’aide de structures de boucles traditionnelles comme la boucle for exige de connaître le nombre exact d’éléments dans la collection et elle permet également d’introduire des erreurs. Ce qui n’est pas du tout souhaité, surtout lorsque l’on traite du Big Data, car en plus de traiter une masse importante de données, la moindre erreur peut être fatale.
Voyons cela avec ce code :
public static void main(String[] args){
List couleurs = new ArrayList();
couleurs.add("Vert");
couleurs.add("Bleu");
couleurs.add("Orange");
// boucle for traditionnelle
for (int i=0; i <3; i++) {
System.out.println(couleurs.get(i));
}
}
Dans un programme prédéfini comme celui que nous venons de voir, il est tout à fait possible d’utiliser une boucle for traditionnel car on initialise à l’avance le contenu de la collection (dans notre exemple, de la liste). Donc ce n’est pas très impactant pour l’ensemble du programme. Mais imaginez que vous ayez à traiter des millions, voire des milliards de données ? Incrémenter chaque valeur une par une est inenvisageable dans ce genre de cas. Et c’est là que for-each devient intéressant.
Ne pas avoir à déclarer un incrément est le principal avantage de la boucle for-each. En fournissant une approche idiomatique, elle réduit l’encombrement et la possibilité d’introduire des erreurs en n’ayant pas à déclarer ou à traiter l’incrément.
Préférez et utilisez la boucle for-each plutôt que la boucle for traditionnelle. À moins que vous n’ayez besoin d’accéder à l’élément d’une position précise d’une collection. Toutefois, de nos jours, cela devient de plus en plus rare.
La méthode ForEach() en Java
Jusqu’à présent, nous avons étudié la boucle for-each. Intéressons-nous maintenant à la méthode forEach().
L’interface Iterable (qui est le parent de l’interface Collection) a une nouvelle méthode appelée forEach() dans Java 8. Elle fournit un autre moyen, plus fonctionnel, d’itérer sur les collections. D’ailleurs, plusieurs autres langages de programmation tels que Scala utilisent cette boucle au sein de leurs codes.
La signature de la méthode forEach() est la suivante:
void forEach(Consumer action)
Selon la Javadoc la méthode forEach() « Effectue une action donnée pour chaque élément de l’Iterable jusqu’à ce que tous les éléments aient été traités ou que l’action lève une exception.«
La meilleure façon de comprendre cette méthode est, comme nous le faisons depuis de début, de donner des exemples.
Exemples d’utilisation de la méthode forEach en Java
Nous allons nous penchez sur plusieurs manières d’utiliser la méthode forEach() donc ouvrez votre IDE et on y va. Et si vous êtes à vos débuts dans la programmation informatique, vous pouvez lire notre article sur la programmation orientée objet afin de vous mettre complètement dans le bain.
Exemple #1 : la méthode forEach() prenant en argument un Consumer
Dans les boucles for traditionnelles utilisant des itérateurs et même avec la boucle la boucle for améliorée, nous parcourons la collection et effectuons l’action manuellement dans la boucle. La méthode forEach() change cela en fournissant un moyen plus concis d’obtenir le même résultat mais en le faisant un peu différemment, plus aligné sur le paradigme de la programmation fonctionnelle.
Si vous regardez la définition de la méthode forEach() de la dernière section, elle prend un argument appelé action. Cet argument spécifie l’action à effectuer sur les éléments de la collection. L’action doit implémenter l’interface Consumer, qui est une interface fonctionnelle :
- Les interfaces fonctionnelles contiennent une seule méthode abstraite.
- Les interfaces fonctionnelles ne renvoient pas de résultat et ne peuvent pas être utilisées comme cible d’affectation pour les expressions Lambda.
Voici l’interface Consumer :
@FunctionalInterface
public interface Consumer {
void accept(T t);
}
Notre exemple consiste à itérer sur une liste de couleur (List<String> couleurs) tout en les affichant. La première des choses à faire est d’instancier l’interface Consumer pour définir l’action que nous voulons exécuter sur les éléments de la collection (affichage des éléments).
Consumer afficherCouleur = new Consumer() {
public void accept(String coleur) {
System.out.println(coleur);
};
};
Nous pouvons maintenant passer afficherCouleur à la méthode forEach() :
couleurs.forEach(afficherCouleur);
Le code complet est le suivant :
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class Programme {
public static void main(String[] args){
List couleurs = new ArrayList();
couleurs.add("Vert");
couleurs.add("Bleu");
couleurs.add("Orange");
Consumer afficherCouleur = new Consumer() {
public void accept(String coleur) {
System.out.println(coleur);
};
};
couleurs.forEach(afficherCouleur);
}
}
Le code ci-dessus produit l’affichage suivant :
Vert
Bleu
Orange
Bien que l’exemple ci-dessus fonctionne, les expressions Lambda sont bien meilleures pour exprimer la même logique et doivent être préférées à l’instanciation et au passage d’objets Consumer. (Il est tout de même bon de connaître l’interface Consumer pour comprendre ce qui se passe sous le capot des expressions Lambda).
Exemple #2 : la méthode forEach() et les lambdas expresssions
Les interfaces fonctionnelles nous permettent d’utiliser les expressions Lambda pour écrire du code concis en évitant l’instanciation d’objets ou des classes anonymes (qui ont l’air encore plus laides surtout quand il y a pas mal de code).
Dans l’exemple de la partie précédente, la méthode forEach() prend un objet Consumer qui est une interface fonctionnelle. Avec cette information, nous pouvons réécrire le code de cet exemple de cette manière :
import java.util.ArrayList;
import java.util.List;
public class Programme {
public static void main(String[] args){
List couleurs = new ArrayList();
couleurs.add("Vert");
couleurs.add("Bleu");
couleurs.add("Orange");
//lambda expression
couleurs.forEach(couleur -> System.out.println(couleur));
}
}
Nous remarquons que la dernière instruction utilisant une lambda expression remplace les instructions du programme précédent utilisant un Consumer. Voilà le pouvoir des lambdas expressions qui sont plus lisibles et concis.
L’utilisation de la boucle for-each et la méthode forEach() en Java sur les collections
On peut utiliser la boucle for-each et la méthode forEach() sur toutes les collections, que ce soit, un tableau, un Map, etc. Nous allons illustrer tout cela à travers des exemples. Ici, on vous montre comment procéder lorsqu’il s’agit d’un tableau et d’un Map.
Utilisation #1 : itération sur les tableaux
Nous pouvons itérer sur des tableaux en utilisant à la fois la boucle for améliorée et la méthode forEach(). Prenons un exemple. Dans cet exemple, nous allons itérer sur une collection (List<String>) en utilisant à la fois la méthode forEach() et la boucle for améliorée.
Itération à l’aide de la boucle for améliorée
List namesOfColumns = Arrays.asList("LastName", "FirstName", "Year");
for (String nameOfColumn : namesOfColumns) {
System.out.println(nameOfColumn);
}
Itération à l’aide de la méthode forEach()
List namesOfColumns = Arrays.asList("LastName", "FirstName", "Year");
namesOfColumns.forEach(nameOfColumn -> System.out.println(nameOfColumn));
// or use method reference
//namesOfColumns.forEach(System.out::println);
Vous remarquerez que le résultat reste le même que ce soit avec la boucle for-each ou avec la méthode forEach().
Utilisation #2 : itération sur les Maps
L’interface Map fournit une variante de la méthode forEach() qui accepte une interface fonctionnelle appelée BiConsumer. Cette interface est une spécialisation de l’interface Consumer que nous avons étudié dans les sections précédentes. Contrairement à Consumer, elle prend deux arguments, d’où le nom BiConsumer. Les Maps utilisent l’interface BiConsumer afin que l’action puisse être effectuée sur la clé et la valeur simultanément.
public static void main(String[] args){
Map mostPopularLanguagesForBigData = new HashMap();
mostPopularLanguagesForBigData.put(1, "Python");
mostPopularLanguagesForBigData.put(2, "R");
mostPopularLanguagesForBigData.put(3, "Java");
mostPopularLanguagesForBigData.forEach((key, value) -> System.out.println(key + " => " + value));
}
L’exécution du code ci-dessus produit l’affichage suivant :
1 => Python
2 => R
3 => Java
Nous pouvons avoir le même résultat en utilisant la boucle for améliorée de la façon suivante :
Map mostPopularLanguagesForBigData = new HashMap();
mostPopularLanguagesForBigData.put(1, "Python");
mostPopularLanguagesForBigData.put(2, "R");
mostPopularLanguagesForBigData.put(3, "Java");
//boucle for améliorée
for (Map.Entry entrySet : mostPopularLanguagesForBigData.entrySet() ) {
System.out.println(entrySet.getKey() + " => " + entrySet.getValue());
}
La méthode forEach() a été introduite pour permettre d’écrire du code en utilisant le style de programmation fonctionnelle.
L’un des principaux avantages de la méthode forEach() est que l’on peut faire des choses fantaisistes assez facilement, comme exécuter le code en parallèle en ajoutant parallelStream().
couleurs.parallelStream().forEach(System.out::println) ;
Pour obtenir le même résultat sans la méthode forEach(), vous devrez écrire votre propre code de multithreading. La méthode forEach() est un itérateur interne. Dans les itérateurs internes, le code qui génère les valeurs décide quand invoquer le code qui utilise cette valeur. C’est l’itérateur qui contrôle l’itération. En d’autres termes, vous dites à la collection « prenez ce code et appliquez-le sur tous les éléments de la collection ».
Maintenant, vous savez comment on utilise la boucle for-each et la méthode forEach() de Java. Nous les avons illustrés dans plusieurs exemples que vous pourriez reproduire lors de votre apprentissage. Cela vous aidera sans doute dans vos projets, que ce soit dans le domaine du Big Data ou bien dans d’autres projets qui nécessitent de la programmation.