Java 8 e fluxos infinitos
1. Visão geral
Neste artigo, veremos uma APIjava.util.Stream e veremos como podemos usar essa construção para operar em um fluxo infinito de dados / elementos.
A possibilidade de trabalhar na sequência infinita de elementos é baseada no fato de que os fluxos são construídos para serem preguiçosos.
Essa preguiça é alcançada pela separação entre dois tipos de operações que podem ser executadas em streams:intermediateeterminal operações.
2. Operações intermediárias e terminais
All Stream operations are divided into intermediate and terminal operationse são combinados para formar pipelines de fluxo.
Um pipeline de fluxo consiste em uma fonte (como umCollection, uma matriz, uma função de gerador, um canal de E / S ou gerador de sequência infinita); seguido por zero ou mais operações intermediárias e uma operação terminal.
2.1. Intermediate Operações
Intermediate operações não são executadas até que alguma operaçãoterminal seja chamada.
Eles são compostos formando um pipeline de execução deStream. A operaçãointermediate pode ser adicionada a um pipelineStream pelos métodos:
-
filtro()
-
mapa()
-
flatMap ()
-
distinto ()
-
classificado ()
-
olhadinha()
-
limite()
-
pular()
Todas as operaçõesIntermediate são lentas, portanto, não são executadas até que o resultado de um processamento seja realmente necessário.
Basicamente, as operaçõesintermediate retornam um novo fluxo. A execução de uma operação intermediária, na verdade, não executa nenhuma operação, mas cria um novo fluxo que, quando percorrido, contém os elementos do fluxo inicial que correspondem ao predicado especificado.
Como tal, a passagem deStream não começa até que a operaçãoterminal do pipeline seja executada.
Essa é uma propriedade muito importante, especialmente importante para fluxos infinitos - porque nos permite criar fluxos que serão realmente chamados somente quando uma operaçãoTerminal for chamada.
2.2. Terminal Operações
Terminal operações podem atravessar o fluxo para produzir um resultado ou um efeito colateral.
Após a operação do terminal, o pipeline de fluxo é considerado consumido e não pode mais ser usado. Em quase todos os casos, as operações do terminal estão ansiosas, concluindo a travessia da fonte de dados e o processamento do pipeline antes de retornar.
A avidez de uma operação terminal é importante em relação a fluxos infinitos porqueat the moment of processing we need to think carefully if our Stream is properly bounded by, por exemplo, uma transformaçãolimit(). Terminal operações são:
-
para cada()
-
forEachOrdered ()
-
toArray ()
-
reduzir()
-
collect ()
-
min ()
-
max ()
-
contagem()
-
anyMatch ()
-
allMatch ()
-
noneMatch ()
-
findFirst ()
-
encontre algum()
Cada uma dessas operações acionará a execução de todas as operações intermediárias.
3. Streams infinitos
Agora que entendemos esses dois conceitos - operaçõesIntermediateeTerminal - somos capazes de escrever um fluxo infinito que aproveita a preguiça dos fluxos.
Digamos que queremos criar um fluxo infinito de elementos de zero que será incrementado por dois. Então, precisamos limitar essa sequência antes de chamar a operação do terminal.
It is crucial to use a limit() method before executing a collect() method que é uma operação de terminal, caso contrário, nosso programa será executado indefinidamente:
// 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));
Criamos um fluxo infinito usando um métodoiterate(). Então chamamos uma transformaçãolimit()e uma operação terminalcollect(). Então, em nossoList, resultante, teremos os primeiros 10 elementos de uma sequência infinita devido a uma preguiça de aStream.
4. Fluxo infinito de um tipo personalizado de elementos
Digamos que queremos criar um fluxo infinito deUUIDs aleatórios.
A primeira etapa para conseguir isso usandoStream API é criar umSupplier desses valores aleatórios:
Supplier randomUUIDSupplier = UUID::randomUUID;
Quando definimos um fornecedor, podemos criar um fluxo infinito usando um métodogenerate():
Stream infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);
Então poderíamos pegar alguns elementos desse fluxo. Precisamos nos lembrar de usar um métodolimit() se quisermos que nosso programa termine em um tempo finito:
List randomInts = infiniteStreamOfRandomUUID
.skip(10)
.limit(10)
.collect(Collectors.toList());
Usamos uma transformaçãoskip() para descartar os primeiros 10 resultados e pegar os próximos 10 elementos. Podemos criar um fluxo infinito de qualquer elemento de tipo personalizado passando uma função de uma interfaceSupplier para um métodogenerate() em umStream.
6. Do-While - a maneira do fluxo
Digamos que temos um loop do..while simples em nosso código:
int i = 0;
while (i < 10) {
System.out.println(i);
i++;
}
Estamos imprimindo o contador dei dez vezes. Podemos esperar que tal construção possa ser facilmente escrita usando a APIStream e, idealmente, teríamos um métododoWhile() em um fluxo.
Infelizmente, não existe tal método em um fluxo e quando queremos alcançar uma funcionalidade semelhante ao loopdo-while padrão, precisamos usar um métodolimit():
Stream integers = Stream
.iterate(0, i -> i + 1);
integers
.limit(10)
.forEach(System.out::println);
Obtivemos a mesma funcionalidade como um loop while imperativo com menos código, mas a chamada para a funçãolimit() não é tão descritiva quanto seria se tivéssemos um métododoWhile() em um objetoStream.
5. Conclusão
Este artigo explica como podemos usarStream API para criar fluxos infinitos. Estes, quando usados junto com transformações comolimit() –, podem tornar alguns cenários um pouco mais fáceis de entender e implementar.
O código que suporta todos esses exemplos pode ser encontrado emGitHub project - este é um projeto Maven, portanto, deve ser fácil de importar e executar como está.