JavaRush /Blogue Java /Random-PT /Pausa para café #177. Um guia detalhado para Java Stream ...

Pausa para café #177. Um guia detalhado para Java Stream em Java 8

Publicado no grupo Random-PT
Fonte: Hackernoon Esta postagem fornece um tutorial detalhado sobre como trabalhar com Java Stream junto com exemplos de código e explicações. Pausa para café #177.  Um guia detalhado para Java Stream em Java 8-1

Introdução aos threads Java em Java 8

Java Streams, introduzidos como parte do Java 8, são usados ​​para trabalhar com coleções de dados. Eles não são uma estrutura de dados em si, mas podem ser usados ​​para inserir informações de outras estruturas de dados, ordenando e canalizando para produzir um resultado final. Nota: É importante não confundir Stream e Thread, já que em russo ambos os termos são frequentemente referidos na mesma tradução “fluxo”. Stream denota um objeto para realizar operações (na maioria das vezes transferência ou armazenamento de dados), enquanto Thread (tradução literal - thread) denota um objeto que permite executar determinado código de programa em paralelo com outras ramificações de código. Como o Stream não é uma estrutura de dados separada, ele nunca altera a fonte de dados. Os fluxos Java possuem os seguintes recursos:
  1. Java Stream pode ser usado usando o pacote “java.util.stream”. Ele pode ser importado para um script usando o código:

    import java.util.stream.* ;

    Usando este código, também podemos implementar facilmente várias funções integradas no Java Stream.

  2. Java Stream pode aceitar entradas de coleções de dados, como coleções e matrizes em Java.

  3. Java Stream não requer alteração da estrutura de dados de entrada.

  4. Java Stream não altera a origem. Em vez disso, ele gera resultados usando métodos de pipeline apropriados.

  5. Java Streams estão sujeitos a operações intermediárias e terminais, que discutiremos nas seções a seguir.

  6. No Java Stream, as operações intermediárias são pipeline e ocorrem em um formato de avaliação lento. Eles terminam com funções de terminal. Este constitui o formato básico para usar Java Stream.

Na próxima seção, veremos os vários métodos usados ​​no Java 8 para criar um Java Stream como e quando necessário.

Criando um fluxo Java em Java 8

Threads Java podem ser criados de diversas maneiras:

1. Criando um fluxo vazio usando o método Stream.empty()

Você pode criar um fluxo vazio para uso posterior em seu código. Se você usar o método Stream.empty() , um stream vazio será gerado, sem conter valores. Esse fluxo vazio pode ser útil se quisermos pular uma exceção de ponteiro nulo em tempo de execução. Para fazer isso você pode usar o seguinte comando:
Stream<String> str = Stream.empty();
A instrução acima irá gerar um fluxo vazio chamado str sem nenhum elemento dentro dele. Para verificar isso, basta verificar o número ou tamanho do stream usando o termo str.count() . Por exemplo,
System.out.println(str.count());
Como resultado, obtemos 0 na saída .

2. Crie um stream usando o método Stream.builder() com uma instância Stream.Builder

Também podemos usar o Stream Builder para criar um fluxo usando o padrão de design do construtor. Ele foi projetado para construção passo a passo de objetos. Vamos ver como podemos instanciar um stream usando Stream Builder .
Stream.Builder<Integer> numBuilder = Stream.builder();

numBuilder.add(1).add(2).add( 3);

Stream<Integer> numStream = numBuilder.build();
Usando este código, você pode criar um fluxo chamado numStream contendo elementos int . Tudo é feito rapidamente graças à instância Stream.Builder chamada numBuilder que é criada primeiro.

3. Crie um fluxo com os valores especificados usando o método Stream.of()

Outra maneira de criar um stream envolve usar o método Stream.of() . Esta é uma maneira simples de criar um fluxo com determinados valores. Ele declara e também inicializa o thread. Um exemplo de uso do método Stream.of() para criar um stream:
Stream<Integer> numStream = Stream.of(1, 2, 3);
Este código criará um fluxo contendo elementos int , assim como fizemos no método anterior usando Stream.Builder . Aqui criamos diretamente um fluxo usando Stream.of() com valores predefinidos [1, 2 e 3] .

4. Crie um fluxo a partir de um array existente usando o método Arrays.stream()

Outro método comum para criar um thread envolve o uso de arrays em Java. O fluxo aqui é criado a partir de um array existente usando o método Arrays.stream() . Todos os elementos da matriz são convertidos em elementos de fluxo. Aqui está um bom exemplo:
Integer[] arr = {1, 2, 3, 4, 5};

Stream<Integer> numStream = Arrays.stream(arr);
Este código irá gerar um numStream contendo o conteúdo de um array chamado arr, que é um array inteiro.

5. Mesclando dois fluxos existentes usando o método Stream.concat()

Outro método que pode ser usado para criar um stream é o método Stream.concat() . É usado para combinar dois threads para criar um único thread. Ambos os fluxos são combinados em ordem. Isso significa que o primeiro thread vem primeiro, seguido pelo segundo thread e assim por diante. Um exemplo dessa concatenação é assim:
Stream<Integer> numStream1 = Stream.of(1, 2, 3, 4, 5);

Stream<Integer> numStream2 = Stream.of(1, 2, 3);

Stream<Integer> combinedStream = Stream.concat( numStream1, numStream2);
A instrução acima criará um fluxo final denominado combinadoStream contendo elementos do primeiro fluxo numStream1 e do segundo fluxo numStream2 um por um .

Tipos de operações com Java Stream

Como já mencionado, você pode realizar dois tipos de operações com Java Stream no Java 8: intermediária e terminal. Vejamos cada um deles com mais detalhes.

Operações intermediárias

As operações intermediárias geram um fluxo de saída e são executadas somente quando encontram uma operação de terminal. Isso significa que as operações intermediárias são executadas lentamente, em pipeline e só podem ser concluídas por uma operação de terminal. Você aprenderá sobre avaliação preguiçosa e pipeline um pouco mais tarde. Exemplos de operações intermediárias são os seguintes métodos: filter() , map() , Different() , peek() , sorted() e alguns outros.

Operações de Terminal

As operações de terminal completam a execução de operações intermediárias e também retornam os resultados finais do fluxo de saída. Como as operações de terminal sinalizam o fim da execução lenta e do pipeline, esse thread não pode ser usado novamente após ter passado por uma operação de terminal. Exemplos de operações de terminal são os seguintes métodos: forEach() , collect() , count() , reduzir() e assim por diante.

Exemplos de operações com Java Stream

Operações intermediárias

Aqui estão alguns exemplos de algumas operações intermediárias que podem ser aplicadas a um Java Stream:

filtro()

Este método é usado para filtrar elementos de um fluxo que corresponde a um predicado específico em Java. Esses itens filtrados constituem um novo fluxo. Vamos dar uma olhada em um exemplo para entender melhor.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> even = numStream.filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(even);
Conclusão:
[98]
Explicação: Neste exemplo, você pode ver que os elementos pares (divisíveis por 2) são filtrados usando o método filter() e armazenados em uma lista de inteiros numStream , cujo conteúdo será impresso posteriormente. Como 98 é o único número inteiro par no fluxo, ele é impresso na saída.

mapa()

Este método é usado para criar um novo fluxo executando funções mapeadas em elementos do fluxo de entrada original. Talvez o novo fluxo tenha um tipo de dados diferente. O exemplo é assim:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> d = numStream.map(n -> n*2) .collect(Collectors.toList()); System.out.println(d);
Conclusão:
[86, 130, 2, 196, 126]
Explicação: Aqui vemos que o método map() é usado para simplesmente duplicar cada elemento do fluxo numStream . Como você pode ver na saída, cada um dos elementos do fluxo foi duplicado com sucesso.

distinto()

Este método é usado para recuperar apenas elementos individuais em um fluxo, filtrando duplicatas. Um exemplo do mesmo é assim:
Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); System.out.println(numList);
Conclusão:
[43, 65, 1, 98, 63]
Explicação: Neste caso, o método Different() é usado para numStream . Ele recupera todos os elementos individuais em numList removendo duplicatas do fluxo. Como você pode ver na saída, não há duplicatas, ao contrário do fluxo de entrada, que inicialmente tinha duas duplicatas (63 e 1).

olhadinha()

Este método é usado para rastrear alterações intermediárias antes de executar uma operação de terminal. Isso significa que peek() pode ser usado para executar uma operação em cada elemento de um fluxo para criar um fluxo no qual outras operações intermediárias possam ser executadas.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Mapped: "+ n)) .collect(Collectors.toList()); System.out.println(nList);
Conclusão:
Mapeado: 430 Mapeado: 650 Mapeado: 10 Mapeado: 980 Mapeado: 630 [430, 650, 10, 980, 630]
Explicação: Aqui o método peek() é usado para gerar resultados intermediários à medida que o método map() é aplicado aos elementos do fluxo. Aqui podemos notar que mesmo antes de usar a operação de terminal collect() para imprimir o conteúdo final da lista na instrução print , o resultado para cada mapeamento de elemento de fluxo é impresso sequencialmente com antecedência.

classificado()

O método sorted() é usado para classificar os elementos de um fluxo. Por padrão, ele classifica os elementos em ordem crescente. Você também pode especificar uma ordem de classificação específica como parâmetro. Um exemplo de implementação deste método é assim:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));
Conclusão:
1 43 ​​​​63 65 98
Explicação: Aqui, o método sorted() é usado para classificar os elementos do fluxo em ordem crescente por padrão (já que nenhuma ordem específica é especificada). Você pode ver que os itens impressos na saída estão organizados em ordem crescente.

Operações de Terminal

Aqui estão alguns exemplos de algumas operações de terminal que podem ser aplicadas a fluxos Java:

para cada()

O método forEach() é usado para iterar todos os elementos de um fluxo e executar a função em cada elemento, um por um. Isso atua como uma alternativa para instruções de loop como for , while e outras. Exemplo:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));
Conclusão:
43 65 1 98 63
Explicação: Aqui o método forEach() é usado para imprimir cada elemento do fluxo um por um.

contar()

O método count() é usado para recuperar o número total de elementos presentes no fluxo. É semelhante ao método size() , que é frequentemente usado para determinar o número total de elementos em uma coleção. Um exemplo de uso do método count() com um Java Stream é o seguinte:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());
Conclusão:
5
Explicação: Como numStream contém 5 elementos inteiros, usar o método count() nele produzirá 5.

coletar()

O método collect() é usado para realizar reduções mutáveis ​​de elementos de fluxo. Ele pode ser usado para remover conteúdo de um fluxo após a conclusão do processamento. Ele usa a classe Collector para realizar reduções .
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> odd = numStream.filter(n -> n % 2 == 1) .collect(Collectors.toList()); System.out.println(odd);
Conclusão:
[43, 65, 1, 63]
Explicação: Neste exemplo, todos os elementos ímpares no fluxo são filtrados e coletados/reduzidos em uma lista chamada ímpar . Ao final, é impressa uma lista dos ímpares.

min() e max()

O método min() , como o nome sugere, pode ser usado em um stream para encontrar o elemento mínimo nele. Da mesma forma, o método max() pode ser usado para encontrar o elemento máximo em um fluxo. Vamos tentar entender como eles podem ser usados ​​com um exemplo:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); int smallest = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Smallest element: " + smallest);
numStream = Stream.of(43, 65, 1, 98, 63); int largest = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Largest element: " + largest);
Conclusão:
Menor elemento: 1 Maior elemento: 98
Explicação: Neste exemplo, imprimimos o menor elemento em numStream usando o método min() e o maior elemento usando o método max() . Observe que aqui, antes de usar o método max() , adicionamos elementos ao numStream novamente . Isso ocorre porque min() é uma operação de terminal e destrói o conteúdo do stream original, retornando apenas o resultado final (que neste caso era o inteiro “menor”).

findAny() e findFirst()

findAny() retorna qualquer elemento do fluxo como Opcional . Se o fluxo estiver vazio, ele também retornará um valor Opcional , que estará vazio. findFirst() retorna o primeiro elemento do fluxo como Opcional . Tal como acontece com o método findAny() , o método findFirst() também retorna um parâmetro Opcional vazio se o fluxo correspondente estiver vazio. Vamos dar uma olhada no exemplo a seguir com base nestes métodos:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); Optional<Integer> opt = numStream.findFirst();System.out.println(opt); numStream = Stream.empty(); opt = numStream.findAny();System.out.println(opt);
Conclusão:
Opcional[43] Opcional.vazio
Explicação: Aqui, no primeiro caso, o método findFirst() retorna o primeiro elemento do fluxo como Opcional . Então, quando o thread é reatribuído como um thread vazio, o método findAny() retorna um opcional vazio .

allMatch() , anyMatch() e noneMatch()

O método allMatch() é usado para verificar se todos os elementos em um fluxo correspondem a um determinado predicado e retorna o valor booleano true se corresponderem, caso contrário, retorna false . Se o fluxo estiver vazio, ele retornará true . O método anyMatch() é usado para verificar se algum dos elementos em um fluxo corresponde a um determinado predicado. Ele retorna verdadeiro se for assim, falso caso contrário. Se o fluxo estiver vazio, ele retornará false . O método noneMatch() retorna verdadeiro se nenhum elemento no fluxo corresponder ao predicado, e falso caso contrário. Um exemplo para ilustrar isso é assim:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); boolean flag = numStream.allMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.anyMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.noneMatch(n -> n==1);System.out.println(flag);
Conclusão:
falso verdadeiro falso
Explicação: Para um fluxo numStream contendo 1 como elemento, o método allMatch() retorna falso porque todos os elementos não são 1, mas apenas um deles é. O método anyMatch() retorna verdadeiro porque pelo menos um dos elementos é 1. O método noneMatch() retorna falso porque 1 realmente existe como um elemento neste fluxo.

Avaliações preguiçosas em Java Stream

A avaliação lenta leva a otimizações ao trabalhar com Java Streams em Java 8. Elas envolvem principalmente o atraso de operações intermediárias até que uma operação de terminal seja encontrada. A avaliação preguiçosa é responsável por evitar desperdícios desnecessários de recursos em cálculos até que o resultado seja realmente necessário. O fluxo de saída resultante de operações intermediárias é gerado somente após a conclusão da operação do terminal. A avaliação lenta funciona com todas as operações intermediárias em fluxos Java. Um uso muito útil da avaliação lenta ocorre ao trabalhar com fluxos infinitos. Dessa forma, evita-se muito processamento desnecessário.

Pipelines em Java Stream

Um pipeline em um Java Stream consiste em um fluxo de entrada, zero ou mais operações intermediárias alinhadas uma após a outra e, finalmente, uma operação terminal. As operações intermediárias em Java Streams são executadas lentamente. Isso torna inevitáveis ​​as operações intermediárias em pipeline. Com pipelines, que são basicamente operações intermediárias combinadas em ordem, a execução lenta torna-se possível. Pipelines ajudam a acompanhar as operações intermediárias que precisam ser executadas após uma operação de terminal ser finalmente encontrada.

Conclusão

Vamos agora resumir o que aprendemos hoje. Neste artigo:
  1. Demos uma olhada rápida no que são Java Streams.
  2. Em seguida, aprendemos muitas técnicas diferentes para criar threads Java em Java 8.
  3. Aprendemos dois tipos principais de operações (operações intermediárias e operações de terminal) que podem ser executadas em fluxos Java.
  4. Em seguida, examinamos detalhadamente vários exemplos de operações intermediárias e terminais.
  5. Acabamos aprendendo mais sobre avaliação preguiçosa e, finalmente, aprendendo sobre pipeline em threads Java.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION