Java 8 et flux infinis

Java 8 et Infinite Streams

1. Vue d'ensemble

Dans cet article, nous examinerons une APIjava.util.Stream et nous verrons comment nous pouvons utiliser cette construction pour fonctionner sur un flux infini de données / éléments.

La possibilité de travailler sur la séquence infinie d'éléments repose sur le fait que les flux sont conçus pour être paresseux.

Cette paresse est obtenue par une séparation entre deux types d'opérations qui pourraient être exécutées sur les flux: les opérationsintermediate etterminal.

2. Opérations intermédiaires et terminaux

All Stream operations are divided into intermediate and terminal operations et sont combinés pour former des pipelines de flux.

Un pipeline de flux se compose d'une source (telle qu'unCollection, un tableau, une fonction de générateur, un canal d'E / S ou un générateur de séquence infinie); suivi de zéro ou plusieurs opérations intermédiaires et d'une opération de terminal.

2.1. OpérationsIntermediate

Les opérationsIntermediate ne sont pas exécutées unité certaines opérationsterminal sont appelées.

Ils sont composés pour former un pipeline d’exécution deStream. L'opérationintermediate peut être ajoutée à un pipelineStream par des méthodes:

  • filtre()

  • carte()

  • flatMap ()

  • distinct()

  • trié ()

  • coup d'oeil ()

  • limite()

  • sauter()

Toutes les opérationsIntermediate sont paresseuses, elles ne sont donc exécutées que lorsque le résultat d'un traitement est réellement nécessaire.

Fondamentalement, les opérationsintermediate renvoient un nouveau flux. L'exécution d'une opération intermédiaire n'effectue aucune opération, mais crée un nouveau flux qui, une fois traversé, contient les éléments du flux initial qui correspondent au prédicat donné.

En tant que tel, le parcours desStream ne commence pas tant que l'opérationterminal du pipeline n'est pas exécutée.

C'est une propriété très importante, particulièrement importante pour les flux infinis - car elle nous permet de créer des flux qui ne seront réellement appelés que lorsqu'une opérationTerminal est appelée.

2.2. OpérationsTerminal

Les opérationsTerminal peuvent traverser le flux pour produire un résultat ou un effet secondaire.

Une fois l'opération de terminal effectuée, le pipeline de flux est considéré comme consommé et ne peut plus être utilisé. Dans presque tous les cas, les opérations de terminal sont impatientes, finissant leur parcours dans la source de données et le traitement du pipeline avant de revenir.

L'empressement d'une opération de terminal est important concernant les flux infinis carat the moment of processing we need to think carefully if our Stream is properly bounded by, par exemple, une transformationlimit(). Les opérations deTerminal sont:

  • pour chaque()

  • forEachOrdered ()

  • toArray ()

  • réduire()

  • collecte()

  • min ()

  • max ()

  • compter()

  • anyMatch ()

  • allMatch ()

  • aucunMatch ()

  • findFirst ()

  • findAny ()

Chacune de ces opérations déclenchera l'exécution de toutes les opérations intermédiaires.

3. Flux infinis

Maintenant que nous comprenons ces deux concepts - les opérationsIntermediate etTerminal - nous sommes en mesure d'écrire un flux infini qui exploite la paresse des flux.

Disons que nous voulons créer un flux infini d'éléments à partir de zéro qui sera incrémenté de deux. Ensuite, nous devons limiter cette séquence avant d'appeler le fonctionnement du terminal.

It is crucial to use a limit() method before executing a collect() method qui est une opération de terminal, sinon notre programme s'exécutera indéfiniment:

// given
Stream infiniteStream = Stream.iterate(0, i -> i + 2);

// when
List collect = infiniteStream
  .limit(10)
  .collect(Collectors.toList());

// then
assertEquals(collect, Arrays.asList(0, 2, 4, 6, 8, 10, 12, 14, 16, 18));

Nous avons créé un flux infini en utilisant une méthodeiterate(). Ensuite, nous avons appelé une transformationlimit() et une opération de terminalcollect(). Ensuite, dans notreList, résultant, nous aurons les 10 premiers éléments d'une suite infinie en raison d'une paresse de aStream.

4. Flux infini d'un type d'éléments personnalisé

Disons que nous voulons créer un flux infini deUUIDs aléatoires.

La première étape pour y parvenir à l'aide de l'APIStream consiste à créer unSupplier de ces valeurs aléatoires:

Supplier randomUUIDSupplier = UUID::randomUUID;

Lorsque nous définissons un fournisseur, nous pouvons créer un flux infini en utilisant une méthodegenerate():

Stream infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);

Ensuite, nous pourrions prendre quelques éléments de ce flux. Nous devons nous rappeler d'utiliser une méthodelimit() si nous voulons que notre programme se termine dans un temps fini:

List randomInts = infiniteStreamOfRandomUUID
  .skip(10)
  .limit(10)
  .collect(Collectors.toList());

Nous utilisons une transformationskip() pour supprimer les 10 premiers résultats et prendre les 10 éléments suivants. Nous pouvons créer un flux infini de n'importe quel élément de type personnalisé en passant une fonction d'une interfaceSupplier à une méthodegenerate() sur unStream.

6. Do-While - la manière du flux

Disons que nous avons une simple boucle do.. while dans notre code:

int i = 0;
while (i < 10) {
    System.out.println(i);
    i++;
}

Nous imprimons dix fois le compteuri. Nous pouvons nous attendre à ce qu'une telle construction puisse être facilement écrite en utilisant l'APIStream et idéalement, nous aurions une méthodedoWhile() sur un flux.

Malheureusement, une telle méthode n'existe pas sur un flux et lorsque nous voulons obtenir des fonctionnalités similaires à la boucle standarddo-while, nous devons utiliser une méthodelimit():

Stream integers = Stream
  .iterate(0, i -> i + 1);
integers
  .limit(10)
  .forEach(System.out::println);

Nous avons obtenu la même fonctionnalité qu'une boucle while impérative avec moins de code, mais l'appel à la fonctionlimit() n'est pas aussi descriptif qu'il le serait si nous avions une méthodedoWhile() sur un objetStream.

5. Conclusion

Cet article explique comment utiliser lesStream API pour créer des flux infinis. Ceux-ci, lorsqu'ils sont utilisés avec des transformations telles quelimit() –, peuvent rendre certains scénarios un peu plus faciles à comprendre et à implémenter.

Le code prenant en charge tous ces exemples peut être trouvé dans leGitHub project - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.