FAQ Langage JavaConsultez toutes les FAQ

Nombre d'auteurs : 41, nombre de questions : 296, dernière mise à jour : 8 mars 2017  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur le forum Java de http://java.developpez.com ainsi que l'expérience personnelle des auteurs.

Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes. Les auteurs font leur maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur, ou que vous souhaitez nous aider en devenant rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.


SommaireCollections et StreamsStreams (9)
précédent sommaire suivant
 

Un stream (data stream ou flux de données ou flot de données) est un concept introduit depuis le JDK 8, et qui représente un ensemble de données. Les flux, en eux-mêmes, ne définissent pas une nouvelle manière de stocker des données ; ils définissent au contraire une nouvelle manière d'appliquer des opérations sur un ensemble de données qui s'apparente, par exemple, à ce qui peut être fait en SQL. Ainsi, il est possible de traiter un ensemble de manière séquentielle ou parallèle, ou même de le diviser en sous-flux. On peut lui appliquer des filtres, des conversions, des réductions, des tris, des traitements ou des opérations arithmétiques ou statistiques. De plus, il est possible de convertir un flux en collection ou tableau. La source même du flux peut être une collection, un tableau ou encore une source de données dynamiques.

Attention : il ne faut pas confondre les flux sur les données (streams ou data streams) et les flux d’entrée sortie (I/O streams) qui sont bien plus anciens dans la JVM.

Un stream permet donc de manipuler simplement un flux de données quelconque. L'utilisation d'un flux se compose de trois étapes :

  1. La création du stream à partir d'une source de données (tableau, Collection, fonction de génération, etc.) ;
  2. Zéro, une ou plusieurs déclarations d'opérations intermédiaires, qui permettent de modifier le comportement du flux de différentes manières. Chacune de ces opérations retourne un nouveau flux, mais ces derniers sont créés de manière lazy (paresseuse) : aucun traitement n'est effectué lors de leur déclaration.
  3. Un appel de l'opération terminale qui va fournir un résultat. C'est elle qui va déclencher le parcours des données et exécuter les différentes opérations intermédiaires.


Remarque : la manipulation du stream n'a aucun impact sur la source de données, mais uniquement sur le résultat du stream.

Par exemple :
Code Java : Sélectionner tout
1
2
3
IntStream.range(0, 10000)                    // 1. Création d'un Stream d'int allant de 0 à 10 000. 
    .filter(i -> i%2==0 && i%3==0 && i%5==0) // 2. Opération intermédiaire : on n'accepte que les valeurs divisibles à la fois par 2, 3 et 5. 
    .forEach(System.out::println);           // 3. Opération finale : on boucle sur les valeurs, pour les afficher avec System.out.println().

Mis à jour le 10 juillet 2015 adiGuba bouye

Non, il n'est pas possible de réutiliser un flux. Les streams sont conçus pour être utilisés via un chaînage de méthodes, et ils ne peuvent être utilisés qu'une seule fois : au moment auquel l'opération terminale est invoquée. Cependant, les opérations terminales iterator() et spliterator() échappent en partie à cette règle en différant l’exécution des opérations intermédiaires jusqu'au moment de la manipulation des itérateurs retournés par ces méthodes.

Mis à jour le 10 juillet 2015 adiGuba bouye

Il existe quatre types de flux :

  • Stream - représente les flux sur des objets ;
  • IntStream - représente un flux spécialisé d'int ;
  • LongStream - représente un flux spécialisé de long ;
  • DoubleStream - représente un flux spécialisé de double.


Il est possible de passer d'un type de flux à un autre en invoquant des opérations intermédiaires de conversion.

Mis à jour le 10 juillet 2015 adiGuba bouye

Il existe plusieurs manières de créer un stream : on peut utiliser une source de données préexistante, ou même une méthode qui nous générera les données au fur et à mesure du parcours du flux (utile pour des flux « infinis »).

Les streams basent leur fonctionnement sur des instances de la classe Spliterator, une sorte de super-itérateur qui peut se découper en deux et conserve certaines caractéristiques de la source de données afin d'optimiser le traitement. En théorie, il faudrait passer par les méthodes statiques de la classe StreamSupport pour créer un Stream à partir d'un Spliterator. Heureusement, l'API standard nous offre plusieurs manières d'obtenir un flux, si bien qu'il sera rare d'avoir à utiliser le Spliterator.

En voici une liste non exhaustive :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Stream vide : 
Stream.empty(); 
  
// Stream infini généré à partir de Math.random() : 
Stream.generate(Math::random); 
  
// Stream à partir d'une liste de valeur 
Stream.of("a", "b", "c"); 
  
// Stream généré dynamiquement, représentant les valeurs de 0 à 100 (compris ou pas) 
IntStream.rangeClosed(0, 100); 
IntStream.range(0, 100); 
  
// Stream représentant la concaténation de deux Streams : 
IntStream.concat( 
    IntStream.range(0, 100),  
    IntStream.of(200, 300, 400, 500)); 
  
// Stream généré dynamiquement, représentant les valeurs numériques via un incrément de 0.5 : 
DoubleStream.iterate(0.0, n -> n + 0.5); 
  
// Stream généré à partir du contenu d'un tableau : 
String[] array = { "a", "b", "c" }; 
Arrays.stream(array); 
  
// Stream généré à partir du contenu d'une collection (List, Set, ...) : 
Collection<String> collection = Arrays.asList("a", "b", "c"); 
collection.stream(); 
// Même chose, mais avec un Stream parallèle : 
collection.parallelStream(); 
  
try (BufferedReader reader : Files.newBufferedReader(Paths.get("in.txt"))) { 
    // Stream obtenu à partir des lignes du fichier : 
    reader.lines(); 
}

L'utilisation la plus fréquente consiste à invoquer la méthode stream() de l'API de Collection, qui permet d'obtenir un Stream se basant sur le contenu de la collection en question.

Mis à jour le 6 juillet 2015 adiGuba

Une opération stateless (sans état) est un type d’opérations intermédiaires qui ne gère aucun état pendant le traitement du flux, ce qui permet de paralléliser les traitements sans trop de surcoûts. Ces opérations se basent uniquement sur l'élément courant du flux, indépendamment des autres éléments.

Cependant, il existe quelques opérations intermédiaires dites stateful (littéralement « pleine d’état ») qui doivent gérer un état pour effectuer correctement leur traitement (par exemple : les opérations skip(), limit(), distinct() et sorted()). Ces opérations peuvent devenir très coûteuses si on manipule un flux de données à la fois ordonné et en parallèle. Dans ce cas-là, il peut être préférable d'utiliser un traitement séquentiel (via l’opération sequential()) ou d'ignorer l'ordre des éléments (via l’opération unordered()), selon ce qui s'adapte le mieux à votre besoin.

Mis à jour le 10 juillet 2015 adiGuba

Une opération short-circuiting (court-circuitante) est une opération intermédiaire qui ne va pas obligatoirement traiter tous les éléments du flux. Par exemple, l’opération limit() va seulement traiter N éléments du flux. L’opération findFirst() va, quant à elle, arrêter dès qu'elle trouvera le premier élément correspondant ; ce qui permet d'utiliser des streams infinis, sans boucler indéfiniment.

Mis à jour le 10 juillet 2015 adiGuba

Il est possible de convertir un flux d'un type A en un flux d'un type B en invoquant une des variantes de l’opération map() de ce flux. Cette opération est intermédiaire.

  • map() - permet de convertir un Stream<T> en Stream<V> ;
  • mapToDouble() - permet de convertir un Stream<T> en DoubleStream ;
  • mapToInt() - permet de convertir un Stream<T> en IntStream ;
  • mapToLong() - permet de convertir un Stream<T> en LongStream.


Par exemple :

Code Java : Sélectionner tout
1
2
3
Stream<Client> fluxDeClient = [...] 
Stream<NumeroDeTelephone> fluxDeClient.map(client -> client.getNumeroDeTelephone()); 
IntStream fluxDAge = fluxDeClient.mapToInt(client -> client.getAge());

De plus, les flux de literals permettent d'obtenir des flux objet du type wrapper équivalent en invoquant l’opération boxed() de ces flux. Cette opération est intermédiaire.

Par exemple :

Code Java : Sélectionner tout
1
2
IntStream fluxLiteral = IntStream.range(0, 10); 
Stream<Integer> fluxWrapper = fluxLiteral.boxed();

Mis à jour le 22 juin 2015 bouye

Il est possible de convertir un flux en un type de stockage arbitraire en invoquant l’opération collect() de ce flux et en passant à cette opération une instance de la classe java.util.stream.Collector<T,A,R>. Cette opération est terminale.

Ici :

  • T est le type des éléments du flux ;
  • A est un type intermédiaire généralement laissé aux soins de l’implémentation de l’opération ;
  • R est le type résultat de l’opération (qui peut être autre chose qu'une collection).


L'API stream fournit de base la classe utilitaire java.util.stream.Collectors qui propose plusieurs méthodes qui fournissent des collecteurs prêts à l'emploi pour convertir un flux en collections :

  • toCollection() - permet la création d'une Collection<V> (collection arbitraire) à partir du flux de type Stream<T> ;
  • toConcurrentMap() - permet la création d'une ConcurrentMap<U, V> (dictionnaire disposant de méthodes synchronisées) à partir du flux de type Stream<T> ;
  • toList() - permet la création d'une List<T> (liste) à partir du flux de type Stream<T> ;
  • toMap() - permet la création d'une Map<U, V> (dictionnaire) à partir du flux de type Stream<T> ;
  • toSet() - permet la création d'un Set<T> (ensemble) à partir du flux de type Stream<T>.


Par exemple :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
List<Integer> listeDEntiers = IntStream.range(0, 10) 
    .boxed() 
    .collect(Collectors.toList()); 
[...] 
Stream<Client> fluxDeClients = [...] 
// Le premier paramètre du générateur sert à générer les clés de l'association ; tandis que le second paramètre sert à générer la valeur associée à cette clé. 
// Ici, l'id du client sert de clé dans l'association. 
// La valeur associée à cette clé est le client lui-même. 
Map<Integer, Client> associationDeClients = fluxDeClients.collect(Collectors.toMap(client -> client.getId(), client -> client)); 
// Ou : 
Map<Integer, Client> associationDeClients = fluxDeClients.collect(Collectors.toMap(client -> client.getId(), Functions.identity()));

Mis à jour le 22 juin 2015 bouye

Il est possible de convertir un flux en un tableau en invoquant une des variantes de l’opération toArray() de ce flux. Cette opération est terminale.

toArray()
Cette version de la méthode, qui n'a pas d'argument, permet de générer un tableau d'objets (Object[]).

Par exemple :

Code Java : Sélectionner tout
1
2
Stream<Client> fluxDeClients = [...] 
Object[] tableauDeClients = fluxDeClients.toArray();

toArray(generator)
Cette seconde version de la méthode prend en paramètre un générateur de tableaux qui permet d'instancier un tableau à la taille requise. Elle renvoie un tableau du même type que les objets contenus dans le flux. Le générateur est une fonction qui prend un entier, la taille du tableau, en paramètre et renvoie un tableau de la taille et du type voulu. Ce générateur peut être, tout simplement, une référence de méthode vers le constructeur de tableaux du type approprié.

Par exemple :

Code Java : Sélectionner tout
1
2
3
4
Stream<Client> fluxDeClients = [...] 
Client[] tableauDeClients = fluxDeClients.toArray(taille -> new Client[taille]); 
// Ou on donne une référence au constructeur de tableaux. 
Client[] tableauDeClients = fluxDeClients.toArray(Client[]::new);

Mis à jour le 22 juin 2015 bouye

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2017 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

 
Responsables bénévoles de la rubrique Java : Mickael Baron - Robin56 -