Domine a API Stream Java: Guia Completo com 5 Recursos Essenciais

Foto do autor

By luis

Em Java, um fluxo representa uma sequência de elementos que podem ser processados de maneira sequencial ou paralela.

Este processamento pode envolver uma série de operações intermediárias, seguidas por uma única operação terminal que produz um resultado final.

O Conceito de Fluxo em Java

A gestão de fluxos em Java é realizada através da API Stream, uma funcionalidade introduzida com o Java 8.

Pense num fluxo como uma linha de montagem industrial, onde matérias-primas (dados) passam por etapas de fabricação, classificação e embalagem antes de serem enviadas. Em Java, essas matérias-primas são objetos ou coleções, as etapas são as operações e a linha de montagem é o fluxo.

Um fluxo em Java é composto por:

  • Uma fonte de dados inicial.
  • Uma sequência de operações intermediárias.
  • Uma operação terminal.
  • O resultado final do processamento.

Vamos explorar algumas características dos fluxos em Java:

  • Um fluxo não é uma estrutura de dados na memória, mas sim uma sequência de arrays, objetos ou coleções, que são processados por métodos específicos.
  • Os fluxos são declarativos, ou seja, você define o que deve ser feito, sem especificar como.
  • Os fluxos só podem ser consumidos uma vez, pois os dados não são armazenados.
  • Um fluxo não altera a estrutura de dados original, apenas gera uma nova estrutura a partir dela.
  • O resultado final é derivado da operação terminal no fluxo.

API de Fluxo versus Processamento de Coleções

Uma coleção é uma estrutura de dados na memória utilizada para armazenar e manipular dados. Coleções fornecem estruturas como Set, Map e List para armazenar informações. Um fluxo, por sua vez, é um método eficaz para transferir dados após serem processados por meio de uma sequência de operações.

Aqui está um exemplo utilizando um ArrayList:

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(0, 3);
        System.out.println(list);
    }
}

Output: 
[3]

Como demonstrado, é possível criar uma coleção ArrayList, armazenar dados e manipulá-los utilizando vários métodos.

Utilizando um fluxo, você pode operar em uma estrutura de dados existente e gerar um novo valor modificado. Abaixo, um exemplo de criação e filtragem de um ArrayList usando um fluxo:

import java.util.ArrayList;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList();

        for (int i = 0; i < 20; i++) {
            list.add(i+1);
        }

        System.out.println(list);

        Stream<Integer> filtered = list.stream().filter(num -> num > 10);
        filtered.forEach(num -> System.out.println(num + " "));
    }
}

#Output

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 

Neste exemplo, um fluxo é criado a partir de uma lista, que é iterada para filtrar valores maiores que 10. Note que o fluxo não armazena dados, apenas itera e imprime o resultado. Tentar imprimir o fluxo diretamente resultaria em uma referência, e não os valores.

Trabalhando com a API Stream do Java

A API Stream do Java processa uma coleção ou sequência de elementos, aplicando operações e produzindo um resultado final. Um fluxo age como um canal por onde os elementos passam, sendo transformados ao longo do caminho.

Um fluxo pode ser originado de várias fontes, como:

  • Coleções (List, Set).
  • Arrays.
  • Arquivos (via buffers).

Existem dois tipos principais de operações aplicadas em um fluxo:

  • Operações Intermediárias.
  • Operações Terminais.

Operações Intermediárias vs Terminais

As operações intermediárias geram um novo fluxo internamente, aplicando transformações à entrada. A iteração real não ocorre nesse estágio, e o fluxo é passado adiante. Somente na operação terminal o fluxo é percorrido para produzir o resultado.

Imagine uma lista de 10 números que você deseja filtrar e mapear. A iteração não ocorre para todos os elementos simultaneamente. Cada elemento é verificado e, caso atenda aos critérios, é mapeado, criando novos fluxos para cada elemento.

A operação de mapeamento é realizada em elementos individuais que passaram pelo filtro, não na lista completa. Apenas durante a operação terminal os elementos são iterados e combinados para formar um único resultado.

Uma vez que a operação terminal é executada, o fluxo é consumido e não pode mais ser utilizado. Um novo fluxo deve ser criado para executar as mesmas operações.

Fonte: The Bored Dev

Com uma visão geral de como os fluxos funcionam, vamos explorar a implementação detalhada em Java.

#1. Criando um Fluxo Vazio

Utilize o método `empty()` da API Stream para criar um fluxo vazio.

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream emptyStream = Stream.empty();
        System.out.println(emptyStream.count());
    }
}

Output:
0

O número de elementos em um fluxo vazio é zero. Fluxos vazios são úteis para prevenir erros do tipo `NullPointerException`.

#2. Criando Fluxos a partir de Coleções

Coleções como List e Set oferecem o método `stream()` que permite criar um fluxo a partir da coleção. Este fluxo pode ser percorrido para obter o resultado desejado.

ArrayList<Integer> list = new ArrayList();

for (int i = 0; i < 20; i++) {
    list.add(i+1);
}

System.out.println(list);

Stream<Integer> filtered = list.stream().filter(num -> num > 10);
filtered.forEach(num -> System.out.println(num + " "));

#Output

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 

#3. Criando Fluxos a partir de Arrays

O método `Arrays.stream()` converte um array em um fluxo.

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] stringArray = new String[]{"this", "is", "etechpt.com"};
        Arrays.stream(stringArray).forEach(item -> System.out.print(item + " "));
    }
}

#Output

this is etechpt.com 

É possível especificar um índice inicial e final para criar o fluxo. O índice inicial é inclusivo, e o final, exclusivo.

String[] stringArray = new String[]{"this", "is", "etechpt.com"};
Arrays.stream(stringArray, 1, 3).forEach(item -> System.out.print(item + " "));

Output:
is etechpt.com

#4. Encontrando Valores Mínimos e Máximos usando Streams

Para encontrar o valor máximo e mínimo de uma coleção ou array, pode-se utilizar comparadores em Java. Os métodos `min()` e `max()` aceitam um comparador e retornam um objeto Optional.

Um objeto Optional pode conter ou não um valor. Se contiver, o método `get()` retorna este valor.

import java.util.Arrays;
import java.util.Optional;

public class MinMax {
    public static void main(String[] args) {
        Integer[] numbers = new Integer[]{21, 82, 41, 9, 62, 3, 11};

        Optional<Integer> maxValue = Arrays.stream(numbers).max(Integer::compare);
        System.out.println(maxValue.get());

        Optional<Integer> minValue = Arrays.stream(numbers).min(Integer::compare);
        System.out.println(minValue.get());
    }
}

#Output
82
3

Recursos de Aprendizagem

Agora que você tem uma compreensão básica de fluxos em Java, aqui estão 5 recursos para se aprofundar no Java 8:

#1. Java 8 em Ação

Este livro oferece um guia sobre os novos recursos do Java 8, incluindo fluxos, lambdas e programação funcional. Questionários e testes ajudam a consolidar o aprendizado.

O livro está disponível em formatos físico e audiolivro na Amazon.

#2. Java 8 Lambdas: Programação Funcional para as Massas

Este livro ensina desenvolvedores Java SE como a introdução de expressões Lambda impacta a linguagem. Inclui explicações claras, exercícios de código e exemplos para ajudar a dominar as expressões lambda do Java 8.

Disponível em versão impressa e para Kindle na Amazon.

#3. Java SE 8 para os Verdadeiramente Impacientes

Este livro é direcionado a desenvolvedores Java SE experientes e aborda as melhorias introduzidas no Java SE 8, incluindo a API Stream, expressões lambda, programação concorrente e alguns recursos do Java 7 menos conhecidos.

Disponível apenas em formato físico na Amazon.

#4. Aprenda Programação Funcional Java com Lambdas e Streams

Este curso da Udemy explora os fundamentos da programação funcional no Java 8 e 9, com foco em expressões lambda, referências de método, fluxos e interfaces funcionais.

Inclui desafios e exercícios práticos relacionados à programação funcional.

#5. Biblioteca de Classes Java

A Biblioteca de Classes Java faz parte da especialização Core Java do Coursera. Ensina a escrever código seguro com Java Generics, a entender a biblioteca de classes (mais de 4.000 classes), trabalhar com arquivos e tratar erros em tempo de execução. Pré-requisitos incluem:

  • Introdução ao Java.
  • Introdução à Programação Orientada a Objetos com Java.
  • Hierarquias Orientadas a Objetos em Java.

Considerações Finais

A API Stream e a introdução de funções Lambda no Java 8 aprimoraram diversos aspectos, como iteração paralela, interfaces funcionais e redução de código.

No entanto, fluxos possuem limitações, como o consumo único. Se você é um desenvolvedor Java, os recursos acima podem aprofundar seu conhecimento nesses tópicos. Explore-os!

Você também pode se interessar por gerenciamento de exceções em Java.