Introduzido no Java 8, o Stream API é usado para processar coleções de objetos. Um fluxo é uma sequência de objetos que oferece suporte a vários métodos que podem ser canalizados para produzir o resultado desejado. Antes de prosseguir, vamos discutir a diferença entre Collection e Streams para entender por que esse conceito foi introduzido.

Java-Stream-Tutorial

Observação: 

  • Se quisermos representar um grupo de objetos como uma entidade única, devemos ir para a coleção .
  • Mas se quisermos processar objetos da coleção, devemos ir para os fluxos.

Se quisermos usar o conceito de streams, stream() é o método a ser usado. O stream está disponível como uma interface.

Stream s = c.stream();

Na pré-tag acima, 'c' se refere à coleção. Portanto, na coleção, estamos chamando o método stream() e, ao mesmo tempo, o estamos armazenando como o objeto Stream. Doravante, desta forma estamos obtendo o objeto Stream.

Nota: Streams estão presentes no pacote de utilitários java denominado java.util.stream

Vamos agora começar com os componentes básicos envolvidos nos fluxos. Eles estão listados e da seguinte forma:

  • Sequência de Elementos
  • Fonte
  • Operações agregadas
  • Pipelining
  • Iteração interna

Recursos do fluxo de Java?

  • Uma corrente não é uma estrutura de dados em vez disso, recebe a entrada a partir das colecções, matrizes , canais ou de I / O.
  • Os fluxos não alteram a estrutura de dados original, eles apenas fornecem o resultado de acordo com os métodos em pipeline.
  • Cada operação intermediária é executada lentamente e retorna um fluxo como resultado, portanto, várias operações intermediárias podem ser canalizadas. As operações de terminal marcam o fim do fluxo e retornam o resultado.

Antes de avançar no conceito, considere um exemplo em que temos ArrayList de inteiros, e suponhamos que aplicamos um filtro para obter apenas números pares do objeto inserido.

Java Streams

Como o Stream funciona internamente?

Em streams,

  • Para filtrar os objetos, temos uma função chamada filter()
  • Para impor uma condição, temos uma lógica de predicado que nada mais é do que uma interface funcional. Aqui, a interface de função pode ser substituída por uma expressão aleatória. Portanto, podemos impor diretamente a condição de check-in em nosso predicado.
  • Para coletar elementos, usaremos Collectors.toList() para coletar todos os elementos necessários.
  • Por fim, armazenaremos esses elementos em uma lista e exibiremos as saídas no console.

Exemplo 

// Java Program to illustrate FILTER & COLLECT Operations
 
// Importing input output classes
import java.io.*;
 
// Importing utility class for List and ArrayList classes
import java.util.*;
 
// Importing stream classes
import java.util.stream.*;
 
// Main class
public class GFG {
 
    // Main driver method
    public static void main(String[] args)
    {
 
        // Creating an ArrayList object of integer type
        ArrayList<Integer> al = new ArrayList<Integer>();
 
        // Inserting elements to ArrayLis class object
        // Custom input integer numbers
        al.add(2);
        al.add(6);
        al.add(9);
        al.add(4);
        al.add(20);
 
        // First lets print the collection
        System.out.println("Printing the collection : "
                           + al);
 
        // Printing new line for better output readability
        System.out.println();
 
        // Stream operations
        // 1. Getting thr stream from this collection
        // 2. Filtering out only even elements
        // 3. Collecting the required elements to List
        List<Integer> ls
            = al.stream()
                  .filter(i -> i % 2 == 0)
                  .collect(Collectors.toList());
 
        // Print the collection after stream operation
        // as stored in List object
        System.out.println(
            "Printing the List after stream operation : "
            + ls);
    }
}
Saída

Imprimindo a coleção: [2, 6, 9, 4, 20]

Imprimindo a lista após a operação de fluxo: [2, 6, 4, 20]

Explicação de saída: Em nosso objeto de coleção, tínhamos elementos inseridos usando a operação add(). Depois de processar o objeto no qual eles foram armazenados por meio de streams, impomos uma condição no predicado de streams para obter apenas elementos pares, obtemos elementos no objeto de acordo com nosso requisito. Conseqüentemente, os fluxos nos ajudaram dessa forma no processamento de objetos de coleção superprocessados.

Várias operações principais no Streams?

Operações do Core Stream

Existem amplamente 3 tipos de operações que são transportadas ao longo dos fluxos, a saber, conforme representado na imagem mostrada acima:

  1. Operações intermediárias
  2. Operações de terminal
  3. Operações de curto-circuito

Vamos discutir as operações intermediárias aqui apenas em fluxos até uma certa profundidade com a ajuda de um exemplo, a fim de descobrir outras operações por meios teóricos. Portanto, existem 3 tipos de operações intermediárias que são as seguintes:

  • Operação 1: método de filtro()
  • Operação 2: método map()
  • Operação 3: método classificado()

Todos os três são discutidos abaixo, visto que caminham lado a lado na maioria dos cenários e para fornecer um melhor entendimento ao usá-los posteriormente, implementando em nossos programas java limpos abaixo. Como já estudamos no exemplo acima, do qual estamos tentando filtrar objetos processados, pode-se interpretar como operação de filtro() operada sobre fluxos. Posteriormente, a partir desses elementos filtrados processados ​​de objetos, estamos coletando os elementos de volta à Lista usando Coletores, para os quais importamos um pacote específico denominado java.util.stream com a ajuda do método Collectors.toList(). Isso é conhecido como operação collect() em fluxos, portanto, aqui novamente, não vamos dar um exemplo para discuti-los separadamente. 

Exemplo:

// Java program to illustrate Intermediate Operations
// in Streams
 
// Importing required classes
import java.io.*;
import java.util.*;
import java.util.stream.*;
 
// Main class
class Test {
 
    // Main driver method
    public static void main(String[] args)
    {
 
        // Creating an integer Arraylist to store marks
        ArrayList<Integer> marks = new ArrayList<Integer>();
 
        // These are marks of the students
        // Considering 5 studemnts so input entries
        marks.add(30);
        marks.add(78);
        marks.add(26);
        marks.add(96);
        marks.add(79);
 
        // Printing the marks of the students before grace
        System.out.println(
            "Marks of students before grace : " + marks);
 
        // Now we want to grace marks by 6
        // using the streams to process over processing
        // collection
 
        // Using stream, we map every object and later
        // collect to List
        // and store them
        List<Integer> updatedMarks
            = marks.stream()
                  .map(i -> i + 6)
                  .collect(Collectors.toList());
 
        // Printing the marks of the students after grace
        System.out.println(
            "Marks of students  after grace : "
            + updatedMarks);
    }
}
Saída
Notas de alunos antes da graça: [30, 78, 26, 96, 79]
Marcas de alunos após a graça: [36, 84, 32, 102, 85]

Nota: Para cada objeto, se houver urgência para fazer algumas operações, seja quadrado, duplo ou qualquer outra que não seja, apenas precisamos usar a operação da função map(), caso contrário, tente usar a operação da função filter(). 

Agora, geeks, vocês estão bem cientes do 'por que' os streams foram introduzidos, mas você deve estar se perguntando 'onde' usá-los. A resposta é muito simples, pois os usamos com muita frequência em nossa vida cotidiana. Conseqüentemente, o geek em palavras mais simples que dizemos pousa diretamente onde o conceito da coleção é aplicável, o conceito de streams pode ser aplicado lá. 

Exemplo da vida real

Exemplo 1: Em geral, no mundo diário, sempre que os dados estão sendo buscados no banco de dados, é mais provável que estaremos usando a coleção, portanto, o próprio conceito de fluxos deve ser aplicado para lidar com os dados processados.

Agora estaremos discutindo exemplos em tempo real para inter-relacionar fluxos em nossa vida. Aqui estaremos pegando os mais amplamente usados, a saber:

  1. Streams em uma mercearia
  2. Streams em rede móvel

Exemplo 2: streams em uma mercearia 

A imagem pictórica acima fornecida é implementada em streams da seguinte forma: 

List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

Exemplo 3: Streams em rede móvel 

Da mesma forma, podemos optar por outro conceito amplamente utilizado que é o modo como lidamos com nossos números de celular. Aqui, não proporemos listagens, apenas demonstraremos como o conceito de stream é invocado nas redes móveis por vários provedores de serviço em todo o mundo.

A coleção pode conter qualquer número de objetos, portanto, deixe 'mobileNumber' ser uma coleção e deixe-o conter vários números de celulares, digamos que ele contenha mais de 100 números como objetos. Suponha agora a única operadora chamada 'Airtel' com a qual devemos enviar uma mensagem se houver alguma migração entre os estados de um país. Portanto, aqui o conceito de streams é aplicado como se, ao lidarmos com todos os números de celular, procurássemos essa operadora usando o método de operação filter() de streams. Desta forma, podemos entregar as mensagens sem olhar para todos os números de celulares e, em seguida, entregar a mensagem que parece impraticável se o fizermos, pois agora já é tarde demais para entregar. Desta forma, essas operações intermediárias, ou seja, filter(), collect(), map() ajudam no mundo real.

Agora, os usuários percebem o poder dos streams em java como se tivéssemos que fazer a mesma tarefa que precisamos mapear para cada objeto, aumentando o comprimento do código, diminuindo a otimização do nosso código. Com o uso de streams, somos capazes de fazer isso em uma única linha, independentemente dos elementos contidos no objeto, pois com o conceito de streams estamos lidando com o próprio objeto.

Nota: filtrar, classificar e mapear, que podem ser conectados para formar um pipeline.