Java Streams vs Vavr Streams

Java Streams vs Vavr Streams

1. introduction

Dans cet article, nous examinerons les différences entre les implémentations deStream en Java et Vavr.

Cet article suppose une familiarité avec les bases deJava Stream API etthe Vavr library.

2. Comparaison

Les deux implémentations représentent le même concept de séquences paresseuses mais diffèrent dans les détails.

Java Streams were built with robust parallelism in mind, offrant un support facile pour la parallélisation. D'autre part, l'implémentation de Vavr favorise un travail pratique avec des séquences de données et ne fournit aucun support natif pour le parallélisme (mais cela peut être obtenu en convertissant une instance en implémentation Java).

C'est pourquoi Java Streams est soutenu par des instancesSpliterator - une mise à niveau vers l'implémentation beaucoup plus ancienne deIterator et de Vavr est soutenue par lesIterator susmentionnés (au moins dans l'une des dernières implémentations).

Les deux implémentations sont vaguement liées à sa structure de données de support et sont essentiellement des façades au-dessus de la source de données que le flux traverse, mais comme l'implémentation de Vavr est basée surIterator- _, _ elle ne tolère pas les modifications simultanées de la collection source.

La gestion par Java des sources de flux permet de modifierwell-behaved stream sources in avant que l’opération de flux de terminal ne soit exécutée.

Malgré la différence fondamentale de conception, Vavr fournit une API très robuste qui convertit ses flux (et autres structures de données) en implémentation Java.

3. Fonctionnalité supplémentaire

L’approche adoptée pour gérer les flux et leurs éléments conduit à d’intéressantes différences dans la manière dont nous pouvons travailler avec eux à la fois en Java et en Vavr.

3.1. Accès aléatoire aux éléments

La fourniture d’API et de méthodes d’accès pratiques aux éléments est l’un des domaines dans lesquels Vavr brille par-dessus l’API Java. Par exemple, Vavr dispose de méthodes fournissant un accès aléatoire aux éléments:

  • get() fournit un accès basé sur un index aux éléments d'un flux.

  • indexOf() fournit la même fonctionnalité d'emplacement d'index que dans JavaList. standard

  • insert() offre la possibilité d'ajouter un élément à un flux à une position spécifiée.

  • intersperse() insérera l'argument fourni entre tous les éléments du flux.

  • find() localisera et retournera un élément à partir du flux. Java fournitnoneMatched qui vérifie simplement l'existence d'un élément.

  • update() will remplace l'élément à un index donné. Cela accepte également une fonction pour calculer le remplacement.

  • search() localisera un élément dans un flux trié (les flux non triés produiront un résultat non défini)

Il est important de se rappeler que cette fonctionnalité est toujours soutenue par une structure de données qui a une performance linéaire pour les recherches.

3.2. Parallélisme et modification concomitante

Alors que les flux de Vavr ne prennent pas nativement en charge le parallélisme comme la méthodeparallel() de Java, il existe la méthodetoJavaParallelStream qui fournit une copie Java parallélisée du flux Vavr source.

Une zone de faiblesse relative dans les flux Vavr est sur le principe deNon-Interference.

En termes simples, les flux Java nous permettent de modifier la source de données sous-jacente jusqu'à ce qu'une opération de terminal soit appelée. Tant qu'une opération de terminal n'a pas été appelée sur un flux Java donné, le flux peut récupérer toutes les modifications apportées à la source de données sous-jacente:

List intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
Stream intStream = intList.stream(); //form the stream
intList.add(5); //modify underlying list
intStream.forEach(i -> System.out.println("In a Java stream: " + i));

Nous verrons que le dernier ajout est reflété dans la sortie du flux. Ce comportement est cohérent que la modification soit interne ou externe au pipeline de flux:

in a Java stream: 1
in a Java stream: 2
in a Java stream: 3
in a Java stream: 5

Nous constatons qu'un flux Vavr ne tolérera pas ceci:

Stream vavrStream = Stream.ofAll(intList);
intList.add(5)
vavrStream.forEach(i -> System.out.println("in a Vavr Stream: " + i));

Ce que nous obtenons:

Exception in thread "main" java.util.ConcurrentModificationException
  at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
  at java.util.ArrayList$Itr.next(ArrayList.java:851)
  at io.vavr.collection.StreamModule$StreamFactory.create(Stream.java:2078)

Les flux Vavr ne sont pas «sains», selon les normes Java. Vavr se comporte mieux avec des structures de données de sauvegarde primitives:

int[] aStream = new int[]{1, 2, 4};
Stream wrapped = Stream.ofAll(aStream);

aStream[2] = 5;
wrapped.forEach(i -> System.out.println("Vavr looped " + i));

Nous donnant:

Vavr looped 1
Vavr looped 2
Vavr looped 5

3.3. Opérations de court-circuit etflatMap()

LeflatMap,, comme l'opérationmap, est une opération intermédiaire dans le traitement de flux - les deux implémentations suiventthe contract of intermediate stream operations - le traitement à partir de la structure de données sous-jacente ne devrait pas avoir lieu tant qu'une opération de terminal n'a pas été appelée.

Cependant, les JDK 8 et 9 comportenta bug qui fait que l'implémentation deflatMap rompt ce contrat et s'évalue avec empressement lorsqu'il est combiné avec des opérations intermédiaires de court-circuit commefindFirst orlimit.

Un exemple simple:

Stream.of(42)
  .flatMap(i -> Stream.generate(() -> {
      System.out.println("nested call");
      return 42;
  }))
  .findAny();

Dans l'extrait de code ci-dessus, nous n'obtiendrons jamais de résultat defindAny carflatMap sera évalué avec empressement, au lieu de simplement prendre un seul élément parmi lesStream. imbriqués

Un correctif pour ce bogue a été fourni dans Java 10.

LeflatMap  de Vavr n'a pas le même problème et une opération fonctionnellement similaire se termine en O (1):

Stream.of(42)
  .flatMap(i -> Stream.continually(() -> {
      System.out.println("nested call");
      return 42;
  }))
  .get(0);

3.4. Fonctionnalité de base de Vavr

Dans certains domaines, il n’existe tout simplement pas de comparaison individuelle entre Java et Vavr; Vavr améliore l'expérience de streaming avec des fonctionnalités qui sont directement inégalées en Java (ou du moins nécessitent une bonne quantité de travail manuel):

  • zip() associe les éléments du flux avec ceux d'unIterable.  fourni Cette opération était auparavant prise en charge dans JDK-8 mais asince been removed after build-93

  • partition() will divise le contenu d'un flux en deux flux, étant donné un prédicat.

  • permutation() as nommé, calculera la permutation (tous les ordres uniques possibles) des éléments du flux.

  • combinations()  donne la combinaison (i.e. sélection possible des éléments) du flux.

  • groupBy renverra un flux sofMap contenant des éléments du flux d'origine, classés par un classificateur fourni.

  • La méthodedistinct dans Vavr améliore la version Java en fournissant une variante qui accepte une expression lambdacompareTo.

Alors que la prise en charge des fonctionnalités avancées est quelque peu sans inspiration dans les flux Java SE,Expression Language 3.0 fournit curieusement une prise en charge pour bien plus de fonctionnalités que les flux JDK standard.

4. Manipulation de flux

Vavr permet une manipulation directe du contenu d'un flux:

  • Insérer dans un flux Vavr existant

Stream vavredStream = Stream.of("foo", "bar", "baz");
vavredStream.forEach(item -> System.out.println("List items: " + item));
Stream vavredStream2 = vavredStream.insert(2, "buzz");
vavredStream2.forEach(item -> System.out.println("List items: " + item));
  • Supprimer un élément d'un flux

Stream removed = inserted.remove("buzz");
  • Opérations basées sur les files d'attente

Le flux de Vavr étant sauvegardé par une file d’attente, il fournit des opérationsprepend etappend à temps constant.

Cependant,changes made to the Vavr stream don’t propagate back to the data source that the stream was created from.

5. Conclusion

Vavr et Java ont tous deux leurs atouts, et nous avons démontré l’engagement de chaque bibliothèque envers ses objectifs de conception - Java pour un parallélisme bon marché et Vavr pour des opérations de flux pratiques.

Grâce à la prise en charge de Vavr pour la conversion entre son propre flux et celui de Java, on peut tirer les avantages des deux bibliothèques dans le même projet sans trop de frais généraux.

Le code source de ce didacticiel est disponibleover on Github.