2023-04-25 13:43 Temps de lecture : 14 min

Comment apprendre l'API Java Stream [+5 Resources]

En Java, un flux représente une succession d'éléments sur lesquels il est possible d'appliquer des opérations, que ce soit de manière séquentielle ou en parallèle.

Un flux peut enchaîner un nombre indéterminé d'opérations intermédiaires, suivies d'une unique opération finale qui produit un résultat.

Définition d'un Flux

Les flux sont gérés par l'API Stream, introduite avec Java 8.

Pour comprendre un flux, imaginez une chaîne de production : des matières premières y sont transformées, triées, puis emballées avant expédition. Dans le contexte Java, ces matières sont des objets ou des collections d'objets, les opérations sont les transformations, le tri et l'emballage, et la chaîne de production correspond au flux.

Un flux est composé des éléments suivants :

  • Une source initiale
  • Des opérations intermédiaires
  • Une opération terminale
  • Un résultat final

Explorons quelques caractéristiques d'un flux en Java :

  • Un flux n'est pas une structure de données en mémoire, mais une séquence d'éléments (tableaux, objets, collections) traités par des méthodes spécifiques.
  • Les flux adoptent une approche déclarative : vous spécifiez ce qu'il faut faire, sans vous soucier de la manière de le faire.
  • Un flux est consommé une seule fois car il ne conserve pas les données.
  • Un flux ne modifie pas la source de données originale, mais génère une nouvelle structure dérivée.
  • Le résultat final est produit par la méthode terminale du flux.

API Stream vs. Traitement des Collections

Une collection est une structure de données en mémoire servant à stocker et manipuler des données. Les collections (Set, Map, List, etc.) fournissent des structures pour stocker les données. Un flux, quant à lui, est un mécanisme pour transférer efficacement des données après les avoir traitées selon un ensemble d'opérations.

Voici un exemple utilisant une collection ArrayList :

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(0, 3);
        System.out.println(list);
    }
}

Output: 
[3]

Comme le montre l'exemple, vous pouvez créer une collection ArrayList, y stocker des données et ensuite interagir avec ces données via différentes méthodes.

Avec un flux, vous agissez sur une structure de données existante pour générer une nouvelle valeur modifiée. L'exemple ci-dessous illustre la création d'une collection ArrayList et son filtrage à l'aide d'un flux.

import java.util.ArrayList;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList();

        for (int i = 0; i < 20; i++) {
            list.add(i+1);
        }

        System.out.println(list);

        Stream<Integer> filtered = list.stream().filter(num -> num > 10);
        filtered.forEach(num -> System.out.println(num + " "));
    }
}

#Output

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 

Dans cet exemple, un flux est créé à partir d'une liste existante. La liste est parcourue pour filtrer les valeurs supérieures à 10. Le flux ne stocke pas les valeurs, mais itère sur la liste et affiche le résultat. Tenter d'afficher le flux lui-même affichera une référence au flux et non les valeurs qu'il contient.

Utilisation de l'API Stream en Java

L'API Stream de Java prend une source d'éléments (collection ou séquence) et lui applique des opérations pour obtenir un résultat. Un flux est donc un canal par lequel les éléments transitent et sont transformés.

Les flux peuvent être créés à partir de diverses sources, telles que :

  • Une collection (List, Set...)
  • Un tableau
  • Des fichiers, par l'intermédiaire de leurs chemins d'accès en utilisant un buffer.

Il existe deux types d'opérations sur les flux :

  • Les opérations intermédiaires
  • Les opérations terminales

Opérations Intermédiaires vs. Terminales

Chaque opération intermédiaire retourne un nouveau flux, transformant les éléments d'entrée selon la méthode spécifiée. La transformation n'est pas immédiate : les modifications sont transmises au flux suivant. Seule l'opération terminale déclenche le traitement effectif du flux, produisant le résultat.

Par exemple, si vous avez une liste de 10 nombres à filtrer puis à transformer, les éléments ne sont pas tous parcourus instantanément pour obtenir le résultat. Au lieu de cela, chaque élément est vérifié et, s'il satisfait la condition, il est transformé. De nouveaux flux sont générés pour chaque étape du traitement.

L'opération de transformation n'est appliquée qu'aux éléments qui passent le filtre. Enfin, l'opération terminale les parcourt et les combine pour produire le résultat final.

Une fois l'opération terminale effectuée, le flux est consommé et ne peut plus être utilisé. Pour effectuer les mêmes opérations, vous devez créer un nouveau flux.

Source : Le Développeur Ennuyé

Maintenant que nous avons une vue d'ensemble du fonctionnement des flux, examinons leur implémentation en Java.

#1. Création d'un Flux Vide

Un flux vide peut être créé via la méthode `empty()` de l'API Stream.

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream emptyStream = Stream.empty();
        System.out.println(emptyStream.count());
    }
}

Output:
0

L'appel à la méthode `count()` sur ce flux retournera 0, car il s'agit d'un flux sans élément. Les flux vides sont utiles pour éviter des exceptions de type NullPointerException.

#2. Créer un Flux à partir d'une Collection

Les collections (List, Set...) exposent une méthode `stream()` permettant de créer un flux à partir de la collection. Le flux créé peut ensuite être traité pour obtenir un résultat.

ArrayList<Integer> list = new ArrayList();

for (int i = 0; i < 20; i++) {
    list.add(i+1);
}

System.out.println(list);

Stream<Integer> filtered = list.stream().filter(num -> num > 10);
filtered.forEach(num -> System.out.println(num + " "));

#Output

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 

#3. Créer un Flux à partir d'un Tableau

La méthode `Arrays.stream()` sert à créer un flux à partir d'un tableau.

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] stringArray = new String[]{"this", "is", "toptips.fr"};
        Arrays.stream(stringArray).forEach(item -> System.out.print(item + " "));
    }
}

#Output

this is toptips.fr 

Il est aussi possible de définir l'indice de début et de fin des éléments du tableau pour créer un flux. L'indice de début est inclusif, et l'indice de fin est exclusif.

String[] stringArray = new String[]{"this", "is", "toptips.fr"};
Arrays.stream(stringArray, 1, 3).forEach(item -> System.out.print(item + " "));

Output:
is toptips.fr

#4. Déterminer les Valeurs Minimale et Maximale avec les Flux

Il est possible de récupérer les valeurs minimale et maximale d'une collection ou d'un tableau en utilisant les comparateurs Java. Les méthodes `min()` et `max()` acceptent un comparateur et retournent un objet Optional.

Un objet Optional est un conteneur qui peut ou non contenir une valeur non nulle. Si l'objet contient une valeur non nulle, l'appel à la méthode `get()` renverra cette valeur.

import java.util.Arrays;
import java.util.Optional;

public class MinMax {
    public static void main(String[] args) {
        Integer[] numbers = new Integer[]{21, 82, 41, 9, 62, 3, 11};

        Optional<Integer> maxValue = Arrays.stream(numbers).max(Integer::compare);
        System.out.println(maxValue.get());

        Optional<Integer> minValue = Arrays.stream(numbers).min(Integer::compare);
        System.out.println(minValue.get());
    }
}

#Output
82
3

Ressources d'Apprentissage

Maintenant que vous avez une compréhension de base des flux en Java, voici 5 ressources pour vous familiariser avec Java 8 :

#1. Java 8 en Action

Ce livre est un guide des nouvelles fonctionnalités introduites dans Java 8, notamment les flux, les lambdas et la programmation fonctionnelle. Des quiz et des questions de vérification de connaissances sont inclus pour vous aider à consolider vos acquis.

Le livre est disponible en version papier et audio sur Amazon.

#2. Java 8 Lambdas : Programmation Fonctionnelle pour Tous

Ce livre est spécifiquement conçu pour montrer aux développeurs Java SE comment l'ajout des expressions Lambda a impacté le langage. Il contient des explications claires, des exercices et des exemples pour vous aider à maîtriser les expressions lambda de Java 8.

Il est disponible en version papier et en édition Kindle sur Amazon.

#3. Java SE 8 pour les Vraiment Impatients

Si vous êtes un développeur Java SE expérimenté, ce livre vous guidera à travers les améliorations apportées dans Java SE 8, l'API Stream, les expressions lambda, le support de la programmation concurrente et certaines fonctionnalités de Java 7 méconnues.

Il est uniquement disponible en version papier sur Amazon.

#4. Apprendre la Programmation Fonctionnelle Java avec Lambdas et Streams

Ce cours Udemy explore les fondations de la programmation fonctionnelle en Java 8 et 9. Les expressions Lambda, les références de méthodes, les flux et les interfaces fonctionnelles sont les concepts clés abordés.

Il inclut également de nombreux puzzles et exercices liés à la programmation fonctionnelle.

#5. La Bibliothèque de Classes Java

La bibliothèque de classes Java fait partie de la spécialisation Core Java offerte par Coursera. Vous apprendrez à écrire du code avec une vérification de type grâce aux Generics de Java, à comprendre les 4000 classes de la bibliothèque, à manipuler les fichiers et à gérer les erreurs d'exécution. Cependant, ce cours nécessite certaines connaissances préalables :

  • Introduction à Java
  • Introduction à la programmation orientée objet avec Java
  • Hiérarchies orientées objet en Java

Conclusion

L'API Stream et l'introduction des expressions Lambda dans Java 8 ont permis de simplifier et d'améliorer de nombreux aspects de Java, tels que l'itération parallèle, les interfaces fonctionnelles, la réduction du code, etc.

Cependant, les flux ont des limitations, notamment le fait qu'ils ne peuvent être consommés qu'une seule fois. Si vous êtes un développeur Java, les ressources mentionnées ci-dessus peuvent vous aider à approfondir ces concepts. N'hésitez pas à les consulter.

Vous pourriez également être intéressé par la gestion des exceptions en Java.

Auteur
France

Rédacteur tech, guides pratiques et astuces numériques.