Stream : à quoi ça sert

Les streams sont des wrappers autour de data source (comme les tableaux, les listes, …).
Ils implementent des opérations nouvelles (utilisant les lambdas).
Ces nouvelles opérations peuvent s’exécuter sequentiellement ou en parallelle.

Les streams ne sont pas des structures de données!
Les streams créer un pipeline d’opérations autour d’une structure de données.

Créer un stream

3 manières classiques :

  • someList.stream()

  • Stream.of(arrayOfObjects)

  • Stream.of(val1, val2, …)

En employant les primitives (int) au lieu des objets wrappers (Integer), il est possible de commettre des erreurs!

#Créer une structure de données depuis un stream

Array

myStream.toArray(Type[]::new)

ex:
employeeStream.toArray(Employee::new)

Collection

List

myStream.collect(Collectors.toList());

ou, plus généralement, en utilisant l’import static :

import java.util.stream.Collectors.*;

...

myStream.collect(toList());

Set

import java.util.stream.Collectors.*;

...

myStream.collect(toSet());

Différentes nature des méthodes d’un stream

Méthodes finales

Aprés l’invocation d’une méthode terminale, le stream est considéré comme consommé et aucune autre opération ne peut plus être effectuée sur ce stream.

On trouve ici:

  • forEach
  • forEachOrdered
  • toArray
  • reduce
  • collect
  • min
  • max
  • count
  • anyMatch
  • allMatch
  • noneMatch
  • findFirst
  • findAny
  • iterator

Méthodes intermédiaires

Ce sont des méthodes qui prennent un stream en entrée et produise un autre stream en sortie.
Elles ne sont pas exécutées tant qu’une méthode finale n’a pas été invoquée.

On trouve ici:

  • map (mapToInt, flatMap, …)
  • filter
  • distinct
  • sorted
  • peek
  • limit
  • skip
  • parallel
  • sequential
  • unordered

Méthodes “court-circuit”

Ces méthodes intérromppent tous les traitements effectués sur un stream dés que **leur traitement à elles est effectué.

On trouve ici :

  • anyMatch
  • allMatch
  • noneMatch
  • findFirst
  • findAny
  • limit
  • skip

Méthodes usuelles des streams

forEach

“forEach” permet d’exécuter une lambda sur chaque élément d’un stream.
Cette lambda est un “Consumer”.

sequenciel:

employees.forEach(e -> e.setSalary(e.getSalary() * 11/10))

parallèle:

  employees.parallel().forEach(e -> e.setSalary(e.getSalary() * 11/10))

forEach est une opération terminale. C’est à dire que forEach consome les éléments du stream, qui ne sont plus disponibles ensuite.

On ne peut pas ecrire:

someStream.forEach(element -> doOneThing(element));
someStream.forEach(element -> doAnotherThing(element));

car dans la 2eme ligne, someStream “n’existe plus” !!

map

“map” permet de transformer (changement de type) chaque élément d’un stream en leur appliquant une lambda.
Cette lambda est une “Function”.

ids.map(EmployeeUtils::findEmployeeById)

Le stream d’entrée contient des “Integer”.
Le stream de sortie contient des “Employee”.

filter

“filter”permet de créer un nouveau stream ne contenant que les élément ayant passer le test désiré, exprimé sous forme de lambda.
Cette lambda est un “Predicate”.

employees.filter(e -> e.getSalary() > 500000)

findFirst

“findFirst” permet de retourner le premier élément d’un stream, en court-circuitant d’autres opérations appliquées sur le stream.
“findFirst” ne prend pas de paramètre et retourne un Optional, car il n’y a pas forcément d’élément correspondant au critère de recherche.

Typiquement, on utilisera un findFirst pour stopper un “filter” dés qu’un élément sera trouvé:

someStream.filter(…).findFirst().orElse(otherValue)

//avec un "map" devant
stream.map(someOp).filter(someTest).findFirst().get()

orElse() est une méthode d’Optional.

En voici les méthodes principales:

  • value.get () - retourne la valeur si présente, sinon lance une exception.
  • value.orElse(other) - retourne la valeur si présente, sinon retourne ‘other’.
  • value.ifPresent(Consumer) - excécute la lambda si la valeur est présente.
  • value.isPresent() - renvoie vrai si la valeur est présenté

findAny

TODO

reduce

Permet de combiner les éléments d’un stream en leur appliquant une lambda.

collect

En combinant un stream et une méthode de la classe Collectors, on peut construire diverses structures de données.

Outre les methodes asList(), asSet() qui permettent de construire respectivement une List et un Set à partir d’un stream, on peut également préciser le type de collection voulue:

  • ArrayList : someStream.collect(toCollection(ArrayList::new))

  • TreeSet : someStream.collect(toCollection(TreeSet::new))

  • Stack : someStream.collect(toCollection(Stack::new))

  • Vector : someStream.collect(toCollection(Vector::new))


on trouve aussi des nouveautés comme StringJoiner, qui produit une String avec le delimiteur fourni :

StringJoiner joiner1 = new StringJoiner(", ");
String result1 = joiner1.add("Java").add("Lisp").add("Ruby").toString();    // "Java, Lisp, Ruby"

syntactic sugar (sans referencer explicitement “StringJoiner”):

String result2 = String.join(", ", "Java", "Lisp", "Ruby");  // "Java, Lisp, Ruby"

Opérations limitant la taille d’un stream

  • limit
  • substream

Opérations de comparaison des éléments d’un stream

  • sorted
  • min, max
  • distinct (supprime les doublon dans un stream)

Opérations de correspondance des éléments d’un stream

  • allMatch : test que tous les élements du stream correspondent à un prédicat.
  • anyMatch : test qu’au moins 1 élement du stream correspondent à un prédicat.
  • noneMatch : test qu’aucuns des élements du stream correspondent à un prédicat.

Ressources



Published

26 September 2014

Tags