Como usar Java Stream API?
Tags: java, beginners, programming
Published: 2023-01-19 12:02:00

Progressivamente mais, vem se tornando um requisito obrigatório criar soluções com processamento rápido, que lidam bem com paralelismo para desenvolver um backend de qualidade, confiabilidade e escalabilidade. 

Por consequência um modelo “novo” (que na verdade é extremamente antigo, já que sua existência pelo que sabe originou-se em 1950 e já era usada no velho Lisp), chamado de programação funcional, vem sendo muito utilizado, por isso novas tecnologias aparecem com esse paradigma como base e outras adicionam suporte a ela (JavaScript, Python, Ruby e etc).

O Java não seria diferente, em março de 2014 foi lançada a versão 8, contando com inúmeras mudanças e o início do suporte ao arquétipo funcional, com novas construções, a adição de expressões lambda, bibliotecas para trabalhar com coleções e também data e hora.

### O que é o Stream API?

Uma dessas adições foi a interface Stream, que veio para melhorar mais as operações com coleções, já que possui vários métodos. 

Por exemplo:

Imagine uma lista numérica, onde você deseja imprimir no console apenas números pares, antes do Java 8, provavelmente seria utilizado o seguinte algoritmo.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/odbsgjlovu2b6tl6w5q5.png)

Você possivelmente utilizaria o foreach para realizar a iteração, porém com essa nova Interface, podemos fazer em apenas duas linhas:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/04it9vcgyu87ya1sifjn.png)

Onde usamos o método stream, para realizar a chamada da interface e usamos o método intermediário “filter()”, passando uma função lambda para filtrar por pares e por fim chamamos um método terminal, para realizar uma determinada operação, que no caso é apenas um “System.out.println”, para exibir no console.

**Benefícios de seu uso**
- Torna seu código conciso;
- Diminuição e facilitação de leitura;
- Fácil de realizar manutenção;
- Não precisa se preocupar com processamento em si, já que o Stream é responsável por iterar, não sua lógica;

**Lembre-se:** Por seguir o paradigma funcional, não se pode esquecer da imutabilidade, isto é, o Stream não irá “modificar” a coleção que está sendo utilizada. Por exemplo, se no caso de, ao invés de listar, queremos salvar na lista apenas os números pares, obrigatoriamente você terá que criar um novo objeto;

**Como funciona a stream**

Stream por definição no Java, é uma “sequência de elementos de uma fonte de dados que oferece suporte a diferentes tipos de operações de agregação”. Ela é uma interface para operarmos com um conjunto de elementos sequenciais de um determinado tipo.
Mas atenção, o Stream não armazena dados, serve para ser esse fluxo de operações para um resultado final.
Imagine o seguinte código de uma coleção usando Streams:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n4ubtsvwo5sq80dkj326.png)

O fluxo de funcionamento de uma Stream é a seguinte:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/glsu9vi60m21pnfsqpau.png)

Se inicia com o dado de origem, que normalmente será uma coleção de itens e irá passar pelo pipeline (conjunto de métodos que fazem operações básicas), por fim irá gerar um resultado através de um dado terminal (que retorna algo que não seja um Stream).
Lembre-se que os dados na stream seguem um paradigma funcional, a cada método usado na pipeline irá gerar uma nova stream.

O stream possui dois tipos de operação, **operações intermediárias (sorted, filter, map e distinct) e operações terminais (forEach, average, collect e allMatch).**

Vamos entender qual a responsabilidade de cada uma delas, quais métodos mais importantes fazem parte delas.

---

### Operações Intermediárias

São os métodos usados para realizar operações que podem modificar o resultado final, aqui encontramos comparadores, ordenadores, modificadores e etc.
Para todos os exemplos, usaremos o objeto Fruta, que é a representação abstrata de uma fruta:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1dh5ff57gzqmkmj9szq6.png)

**Sorted**
Como o nome sugere, utilizamos para ordenar a stream. Com ele pode ordenar de uma forma específica utilizando, por exemplo, a interface Comparator.

Exemplo de uso:

Imagine que eu tenho um carrinho de compras de uma feira de frutas, e eu decido ordenar em ordem alfabética.
Podemos utilizar o sorted, passando a interface Comparator, com o tipo de dado que iremos ordenar, no caso por ser uma String, vai se tornar alfabético.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s4pq4xny39qw483syr40.png)

No console irá ficar:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zme5f72vjzxtq1dzjm62.png)

**Filter**

O método filter é muito útil no dia a dia, quando trabalhamos com consultas, ou queremos filtrar qualquer tipo de dado.
Ele recebe como parâmetro uma interface funcional (Predicate<T>) que define uma função onde precisamos devolver um boolean. Dessa forma iremos receber como retorno uma nova stream que foi filtrada por nosso predicate.
Exemplo de uso:

Temos um carrinho de compras de frutas e desejamos apenas frutas que começam com a letra A.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dns0sqh9gowq8792eelr.png)

No console irá ficar:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lf11mwcnhqyg52g6f590.png)

**Map**

Quando necessitamos fazer algumas transformações em alguma coleção de dados, o método map é o ideal.
Assim como o filter, ele também possui como argumento uma interface funcional que é o Function.
Essa função irá pegar todos os elementos da stream como parâmetro e depois irá retornar os elementos que passaram pelo processo, em uma nova stream.

Exemplo de uso:

No meu carrinho de frutas, descobri que preciso adicionar a palavra “fruta-”, antes do nome das frutas. Podemos utilizar o map para modificar os itens:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r0f2v36308dsnv1hr6cl.png)

No console irá ficar:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6l48idxpevylksnvn2jm.png)

**Distinct**

A ideia é ser igual ao da linguagem SQL, retorna uma nova stream contendo elementos não repetidos, porém tem um detalhe, os itens da coleção que você está utilizando, precisam ter o método equals implementado, pois ele usa ele para realizar o algoritmo.

Exemplo de uso:

Imagine que no seu carrinho de compras, você não quer que tenha duas frutas iguais, caso o cliente esteja comprando duas, haverá uma variável de quantidade que irá computar.

Primeiro, para usarmos o distinct, adicionaremos os métodos hashcode e equals em nosso objeto Fruta:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m8k8bjsbmu66ok0wolw4.png)

Agora, aplicando o distinct:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/022c309t1udthpv1w1k0.png)

No console fica:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/de35pm40qcpx6i43uhbs.png)

---

### Operações terminais

São métodos finais do fluxo Stream, usado para te retornar um resultado diferente de uma nova stream, como no caso dos intermediários (List, String, Long, Integer e etc).

Seus métodos mais importantes são:

**ForEach**
Assim como o foreach original do Java, ele segue a mesma ideia, nele é possível realizar um loop em todos os elementos e realizar algum tipo de processamento.
Exemplo de uso:
Até agora usei praticamente em todos os exemplos das operações intermediárias, para poder imprimir no console as frutas, praticamente essa é a ideia, ele permite receber funções lambda.
No exemplo, usei o println:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rhc0lp9a26ai9zr2a95k.png)

**Average**

É utilizado para calcularmos a média de valores de um elemento. Considerado uma operação de redução, já que ele irá retornar um único elemento.

Exemplo de uso:

Vamos adicionar mais uma propriedade no objeto Fruta, será o de “preço”:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qrqq418vpcvil4fgf2e8.png)

Agora imagine o seguinte caso, no carrinho de compras, com o preço agora, queremos saber a média gasta pelo cliente ficando da seguinte forma:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0vieod5zr5821urt1akz.png)

No console fica:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/orq2hqgv9frhpynh3fi5.png)

**Collect**

Método que possibilita obtermos os itens da Stream que estamos mexendo e transformarmos em coleções (List, Set e Map). Normalmente colocamos como argumento o Collectors (exemplo em uma lista, “Collector.toList()”).

Exemplo de uso:

Imagine que eu queira trabalhar com apenas frutas que começam com a letra “M” e decido criar uma nova lista com elas.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yog7i4ma0ai3xu9gfn1u.png)

No console fica:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3f1iimqlcvf5by5a2y2.png)

**Count**
Assim como o size das coleções, esse método irá retornar quantos elementos temos em uma stream. Retornar um único valor.

Exemplo de uso:

Usando o exemplo anterior, irei querer saber ao invés da lista de frutas, apenas quantos tem:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ahgk9ld3y2h4t43eubpu.png)

No console fica:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ngw3z8qx6iseozwxeq8e.png)

**AllMatch**

Esse método é responsável por garantir se todos os elementos estão de acordo com uma condição. Assim como o filter, ele recebe a interface funcional Predicate, com a função, assim ele irá retornar um boolean.

Exemplo de uso:

Por exemplo, se eu utilizar ele na minha lista de frutas original, ele deve retornar false, pois, nem todas começam com a letra “M”:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mm41xjelex1vnmaydu6p.png)

No console:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bgtgewj3jj0hf4ubuz8s.png)

Agora se filtrarmos:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/he7r0n5vmb06mmwmwn2h.png)

No console fica:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gtozs4eugk9vtjb1tiiv.png)

---

### Conclusão

O Stream do Java veio para ajudar ainda mais nós desenvolvedores, através do seu fluxo de desenvolvimento, com uma variável de origem, seguida de métodos intermediários (pipeline) que irão gerar novas versões da Stream e finalizamos com um método terminal para gerar o resultado que queremos.

Espero que tenha ficado claro o uso dessa API incrível, agora não se esqueça de aplicar em seus projetos e se tornar um desenvolvedor com um código cada vez melhor!