Vavr Streams vs Java Streams

Vavr Streams vs Java Streams

1. Introdução

Neste artigo, veremos como as implementações deStream diferem em Java e Vavr.

Este artigo pressupõe familiaridade com os conceitos básicos deJava Stream API ethe Vavr library.

2. Comparação

Ambas as implementações representam o mesmo conceito de sequências preguiçosas, mas diferem em detalhes.

Java Streams were built with robust parallelism in mind, fornecendo suporte fácil para paralelização. Por outro lado, a implementação do Vavr favorece o trabalho prático com sequências de dados e não oferece suporte nativo ao paralelismo (mas isso pode ser alcançado através da conversão de uma instância em uma implementação Java).

É por isso que Java Streams são apoiados por instâncias deSpliterator - uma atualização para osIterator muito mais antigos e a implementação de Vavr é apoiada pelosIterator mencionados anteriormente (pelo menos em uma das implementações mais recentes).

Ambas as implementações estão vagamente ligadas à sua estrutura de dados de apoio e são essencialmente fachadas sobre a fonte de dados que o fluxo atravessa, mas como a implementação de Vavr é baseada emIterator-_, _ não tolera modificações simultâneas da coleção de origem.

O manuseio do Java de fontes de fluxo torna possível quewell-behaved stream sources in seja modificado antes que a operação de fluxo de terminal seja executada

Não obstante a diferença fundamental de design, o Vavr fornece uma API muito robusta que converte seus fluxos (e outras estruturas de dados) para implementação em Java.

3. Funcionalidade Adicional

A abordagem para lidar com fluxos e seus elementos leva a diferenças interessantes nas maneiras como podemos trabalhar com eles no Java e no Vavr

3.1. Acesso a elemento aleatório

O fornecimento de API e métodos de acesso convenientes aos elementos é uma área que o Vavr realmente brilha sobre a API Java. Por exemplo, o Vavr possui alguns métodos que fornecem acesso aleatório a elementos:

  • get() fornece acesso baseado em índice a elementos de um fluxo.

  • indexOf() fornece a mesma funcionalidade de localização de índice que no JavaList. padrão

  • insert() fornece a capacidade de adicionar um elemento a um fluxo em uma posição especificada.

  • intersperse() irá inserir o argumento fornecido entre todos os elementos do fluxo.

  • find() irá localizar e devolver um item de dentro do fluxo. Java fornecenoneMatched que apenas verifica a existência de um elemento.

  • update() de lavagem substituirá o elemento em um determinado índice. Isso também aceita uma função para calcular a substituição.

  • search() irá localizar um item em um fluxo classificado (fluxos não classificados produzirão um resultado indefinido)

É importante lembrar que essa funcionalidade ainda é apoiada por uma estrutura de dados que tem um desempenho linear para pesquisas.

3.2. Paralelismo e modificação simultânea

Embora os fluxos de Vavr não suportem nativamente o paralelismo como o métodoparallel() de Java, existe o métodotoJavaParallelStream que fornece uma cópia paralelizada baseada em Java do fluxo de origem Vavr.

Uma área de fraqueza relativa nos fluxos Vavr está no princípio deNon-Interference.

Simplificando, os fluxos Java nos permitem modificar a fonte de dados subjacente até que uma operação de terminal seja chamada. Contanto que uma operação de terminal não tenha sido chamada em um determinado fluxo Java, o fluxo pode captar quaisquer alterações na fonte de dados subjacente:

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));

Descobriremos que a última adição é refletida na saída do fluxo. Esse comportamento é consistente se a modificação é interna ou externa ao pipeline de fluxo:

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

Descobrimos que um stream Vavr não tolera isso:

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

O que temos:

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)

Os fluxos Vavr não são "bem-comportados", pelos padrões Java. O Vavr se comporta melhor com estruturas de dados de backup primitivas:

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

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

Dando-nos:

Vavr looped 1
Vavr looped 2
Vavr looped 5

3.3. Operações de curto-circuito eflatMap()

OflatMap,, como a operaçãomap, é uma operação intermediária no processamento de fluxo - ambas as implementações seguemthe contract of intermediate stream operations - o processamento da estrutura de dados subjacente não deve ocorrer até que uma operação de terminal seja chamada.

JDK 8 e 9, entretanto, apresentama bug que faz com que a implementaçãoflatMap quebre este contrato e avalie avidamente quando combinada com operações intermediárias de curto-circuito comofindFirst orlimit.

Um exemplo simples:

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

No snippet acima, nunca obteremos um resultado defindAny porqueflatMap será avaliado avidamente, em vez de simplesmente pegar um único elemento doStream. aninhado

Uma correção para esse bug foi fornecida no Java 10.

flatMap  de Vavr não tem o mesmo problema e uma operação funcionalmente semelhante é concluída em O (1):

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

3.4. Funcionalidade básica do Vavr

Em algumas áreas, simplesmente não há uma comparação direta entre Java e Vavr; O Vavr aprimora a experiência de streaming com uma funcionalidade que é diretamente incomparável em Java (ou pelo menos requer uma boa quantidade de trabalho manual):

  • zip() emparelha itens no fluxo com aqueles de umIterable.  fornecido. Esta operação costumava ser compatível com JDK-8, mas temsince been removed after build-93

  • partition() will divide o conteúdo de um riacho em dois riachos, dado um predicado.

  • permutation() as nomeado, calculará a permutação (todas as ordenações únicas possíveis) dos elementos do fluxo.

  • combinations()  dá a combinação (ou seja, possível seleção de itens) do fluxo.

  • groupBy retornará umMap of streams contendo elementos do stream original, categorizados por um classificador fornecido.

  • O métododistinct em Vavr melhora a versão Java, fornecendo uma variante que aceita uma expressão lambdacompareTo.

Enquanto o suporte para funcionalidade avançada não é inspirado em fluxos Java SE,Expression Language 3.0 estranhamente fornece suporte para muito mais funcionalidade do que fluxos JDK padrão.

4. Manipulação de fluxo

O Vavr permite a manipulação direta do conteúdo de um fluxo:

  • Inserir em um fluxo Vavr existente

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));
  • Remover um item de um fluxo

Stream removed = inserted.remove("buzz");
  • Operações baseadas em fila

Pelo fluxo do Vavr ser apoiado por uma fila, ele fornece operaçõesprependeappend em tempo constante.

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

5. Conclusão

Vavr e Java têm seus pontos fortes, e demonstramos o compromisso de cada biblioteca com seus objetivos de design - Java para paralelismo barato e Vavr para operações de fluxo convenientes.

Com o suporte do Vavr para a conversão entre seu próprio fluxo e o do Java, pode-se derivar os benefícios de ambas as bibliotecas no mesmo projeto sem muita sobrecarga.

O código-fonte deste tutorial está disponívelover on Github.