devstory

Le Tutoriel de Java Stream

  1. Stream
  2. Stream.filter(Predicate)
  3. Stream.sorted(Comparator)
  4. Stream.map(Function)
  5. Stream.flatMap(Function)

1. Stream

Java 8 introduit un nouveau concept appelé Stream (le flux). La première fois que vous lisez sur l'API Stream, cela peut provoquer une confusion, car son nom est similaire à InputStream et OutputStream. Mais Java 8 Stream est quelque chose de complètement différent. Stream est une monade qui joue donc un rôle important dans l'introduction de la programmation fonctionnelle dans Java.
Avant de commencer cet article, je vous recommande de vous renseigner sur les functional interfaces et certaines functional interfaces courantes telles que: Supplier, Consumer, Predicate. Ci-dessous mes articles:
En programmation fonctionnelle, une monade est une structure qui représente un calcul lequel nécessite une séquence d'étapes liées entre elles. Vous pouvez consulter l'exemple de monade ci-dessous:
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
      .stream() // (1)  return a Stream
      .filter(s -> s.startsWith("c")) // (2)  return a new Stream
      .map(String::toUpperCase)  // (3)  return a new Stream
      .sorted()   // (4)  return a new Stream
      .forEach(System.out::println); // (5)
Output:
C1
C2
  • Créer un Streamà partir d'un objet List.
  • Créer un nouveau Streamà partir du Streamprécédent et inclure uniquement les éléments initiés par la lettre "c".
  • Créer un nouveau Streamà partir du Streamprécédent avec tous les éléments convertis en majuscules.
  • Créer un nouveau Stream à partir du Stream précédent en triant les éléments.
  • Imprimer les éléments du dernier Stream.
Dans l'exemple précédent, les étapes (2) à (4) sont des opérations intermédiaires, car elles renvoient un objet Stream. Vous pouvez donc appeler une autre méthode de Stream sans avoir à la terminer par un point-virgule.
Une opération de terminal est une méthode qui renvoie void ou renvoie un type différent de Stream. Dans l'exemple précédent, l'étape 5 est une opération de terminal car la méthode Stream.forEach renvoie void.
Ci-dessous les caractéristiques et avantages de Java 8 Stream:
  • Pas de stockage. Un Stream n'est pas une structure de données, mais une vue d'une source de données (qui peut être un tableau, une liste ou une I/O Channel,...).
  • Un Stream est de nature fonctionnelle. Les modifications apportées à un Streamne modifieront pas les sources de données. Par exemple, filtrer un Stream ne supprimera aucun élément, mais créera un nouveau Stream incluant les éléments filtrés.
  • Exécution paresseuse. Les opérations sur un Streamne seront pas exécutées immédiatement. Ils seront exécutés lorsque les utilisateurs ont absolument besoin de résultats.
  • Consommable. Les éléments d'un Stream ne sont visités qu'une seule fois au cours de la vie d'un Stream. Une fois traversé, un Stream est invalidé, tout comme un Iterator. Il faut régénérer un nouveau Stream si vous souhaitez traverser à nouveau le Stream.
Observer l'illustration ci-dessous pour mieux comprendre la manière de fonction de Stream.
  • Créer un Streamà partir d'une collection.
  • Filtrer les couleurs autres que le rouge.
  • Peindre le rose pour les triangles.
  • Filtrer les formes qui ne sont pas carrées.
  • Calculer la superficie totale.
La classe Employee sera présente dans certains exemples de cet article:
Employee.java
package org.o7planning.stream.ex;

public class Employee {

    private String name;
    private float salary;
    private String gender; // "M", "F"

    public Employee(String name, float salary, String gender) {
        this.name = name;
        this.salary = salary;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public float getSalary() {
        return salary;
    }

    public String getGender() {
        return gender;
    }

    public boolean isFemale() {
        return "F".equals(this.getGender());
    }
}

2. Stream.filter(Predicate)

Renvoyer un Stream constitué d'éléments de ce Stream correspondant au Predicatedonné.
Stream<T> filter(Predicate<? super T> predicate)
Exemple: A partir d'une liste d'employés (Employee), imprimer une liste d'employées dont le salaire est supérieur à 2500.
Stream_filter_ex1.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Stream_filter_ex1 {

    public static void main(String[] args) {

        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);
        
        // Employee is Female and salary > 2500
        Predicate<Employee> predicate = e -> e.isFemale() && e.getSalary() > 2500;

        employees //
             .stream() //
             .filter(predicate) //
             .forEach(e -> System.out.println(e.getName()+ " : " + e.getSalary()));
    }
}
Output:
Mary T. : 5000.0
Sophia B. : 7000.0
Si une méthode n'est pas statique (méthode non statique), ne dispose pas de paramètres et renvoie une valeur boolean, sa référence est alors considérée comme un Predicate. (Voir l'explication dans mon article sur Java Predicate).
Exemple: Créer un Predicate à partir de la référence d'une méthode:
Predicate<Employee> p = Employee::isFemale;
 
// Same as:
 
Predicate<Employee> p = employee -> employee.isFemale();
Stream_filter_ex2.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_filter_ex2 {

    public static void main(String[] args) {
        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);

        employees //
             .stream() //
             .filter(Employee::isFemale) //
             .forEach(e -> System.out.println(e.getName()+ " : " + e.getSalary()));
    }
}
Output:
Sarah M. : 2000.0
Mary T. : 5000.0
Sophia B. : 7000.0

3. Stream.sorted(Comparator)

Renvoyer un Stream constitué d'éléments de ce Stream, triés en fonction du Comparatorfourni.
Stream<T> sorted(Comparator<? super T> comparator)
  • Le Tutoriel de Java Comparator
Exemple: Trier des salariés par ordre croissant de salaire:
Stream_sort_ex1.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_sort_ex1 {

    public static void main(String[] args) {
        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);

        employees //
             .stream() //
             .sorted (
                 (e1,e2) -> (int) (e1.getSalary() - e2.getSalary())
              ) //
             .forEach(e -> System.out.println(e.getSalary() + " : " + e.getName()));
    }
}
Output:
1500.0 : John P.
1700.0 : Charles B.
2000.0 : Sarah M.
5000.0 : Mary T.
7000.0 : Sophia B.
Exemple: Trier l'employé par genre et salaire:
Stream_sort_ex2.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_sort_ex2 {

    public static void main(String[] args) {
        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);

        employees //
             .stream() //
             .sorted (
                 (e1,e2) -> {
                     int v = e1.getGender().compareTo(e2.getGender());
                     if(v == 0) {
                         v = (int) (e1.getSalary() - e2.getSalary());
                     }
                     return v;
                 }
              ) //
             .forEach(e -> System.out.println(e.getGender()+ " : "+ e.getSalary() + " : " + e.getName()));
    }
}
Output:
F : 2000.0 : Sarah M.
F : 5000.0 : Mary T.
F : 7000.0 : Sophia B.
M : 1500.0 : John P.
M : 1700.0 : Charles B.

4. Stream.map(Function)

Renvoyer un nouveau Stream constitué de résultats de l'application de Functiondonnée aux éléments de ce Stream.
<R> Stream<R> map(Function<? super T,? extends R> mapper)
Exemple: Convertir une liste de String(s) en majuscules.
Stream_map_ex1.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Stream_map_ex1 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c", "d", "e");
        
        List<String> newList = list //
                    .stream() // a Stream
                    .map(s -> s.toUpperCase()) // a new Stream
                    .collect(Collectors.toList()); // Stream => List

        System.out.println(list); // [a, b, c, d, e]
        System.out.println(newList); // [A, B, C, D, E]
    }
}
Si une méthode est non statique (méthode non statique), sans paramètre et renvoie une valeur, sa référence est considérée comme une Function. (Voir plus d'explications dans mon article sur la Java Function).
// Create a Function from a method reference:
Function<String, String> f1 = String::toUpperCase;
// Same as:
Function<String, String> f2 = s -> s.toUpperCase();
Stream_map_ex2.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Stream_map_ex2 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c", "d", "e");
        
        List<String> newList = list //
                    .stream() // a Stream
                    .map(String::toUpperCase) // a new Stream
                    .collect(Collectors.toList()); // Stream => List  

        System.out.println(list); // [a, b, c, d, e]
        System.out.println(newList); // [A, B, C, D, E]
    }
}
Exemple: Doubler le salaire pour chaque employé d'une liste:
Stream_map_ex3.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_map_ex3 {

    public static void main(String[] args) {

        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);
        
        employees //
             .stream() // a Stream.
             .map((e) -> new Employee(e.getName(), e.getSalary()* 2, e.getGender())) // a new Stream.
             .forEach(c -> System.out.println(c.getName()+ " : " + c.getSalary()));
    }  
}
Output:
John P. : 3000.0
Sarah M. : 4000.0
Charles B. : 3400.0
Mary T. : 10000.0
Sophia B. : 14000.0

5. Stream.flatMap(Function)

public class Stream<T> {

    <R> Stream<R> flatMap​(Function<? super T,​? extends Stream<? extends R>> mapper);
    
    // .....
}
La méthode Stream.flatMap est très utile. Il y a beaucoup de choses à dire, j'ai donc expliqué dans un autre article:
  • Java Stream.flatMap

Java Basic

Show More