Streams do Java
Tags: java, webdev, beginners, learning
Published: 2024-05-23 11:00:00

Lançada na versão 8 do Java, Stream API é um recurso que traz métodos e classes e tem por finalidade facilitar a manipulação das Collections no estilo da programação funcional (não sabe o que é? dê uma olhada no meu artigo O que é um Paradigma de Programação). 

A seguir temos um trecho de código, na forma imperativa, cujo propósito é somar os inteiros pares de uma lista.

```
private static int somaIterator(ListList<Integer> list) {
   int soma = 0;
   for (int num : list) {
       if (num % 2 == 0) {
           soma += num;
       }
   }
   return soma;
}
```
Esse tipo de abordagem acaba sendo tediosa, você já deve ter implementado diversos for como esse. Para solucionar este problema, a programação funcional nos permite com encadeamento de métodos e passagem de parâmetros via Expressões Lambda.

Os beneficios dessa abordagem é a imutabilidade de variavéis, a variavél não vai mudar dentro do fluxo, além de um código preciso e claro.

```
private static int soma(List<Integer> list) {
    int soma = list.stream()
         .filter(n -> n % 2 == 0)
         .reduce(0, Integer::sum);
    return soma;
}
```
Aqui obtemos uma stream da lista por meio do método _stream()_ da interface Collection. O método _filter()_ retorna apenas os elementos pares. Tais elementos são processados pelo método _reduce()_ que aplica a operação de soma com todos os elementos. O resultado é armazenado no inteiro soma;

## Caracteristicas de uma Stream
Uma Stream pode ser definida como uma sequência de elementos de uma fonte de dados que oferece suporte a diferentes tipos de operações de agregação. Vamos entender essa definição:
- Uma stream provê uma interface para um conjunto sequencial de valores de um determinado tipo. Contudo, streams não armazenam elementos;
- Streams consomem dados de uma fonte, como coleções, arrays ou até recursos de entrada e saída;
- Streams suportam operações comuns a linguagens de programação funcionais, como filtrar, modificar, transformar o elemento em outro e assim por diante. Essas operações podem ser realizadas em série ou em paralelo.

Streams são projetadas de tal maneira que a maior parte de suas operações retornam novas streams. Dessa forma, é possível criar uma cadeia de operações que formam um fluxo de processamento. A isso damos o nome de pipeline.

As operações sobre streams são classificadas em **intermediárias ou terminais** e quando combinadas formam uma estrutura de processo chamada pipeline. As operações intermediárias servem como entrada para outras operações intermediárias ou terminais, elas sempre retornam uma nova stream. As operações terminais são usadas no final da cadeia (de operações) para fechar o processo e retornam um valor ou um objeto, não é possível utilizar intermediárias depois de uma terminal.

Em suma, a Stream API funciona convertendo uma fonte de dados em stream, realizando as operações intermediárias e por fim retorna algo com a chamada da operação terminal. No exemplo do trecho de código acima, temos _filter()_ uma operação terminal e _reduce()_ uma operação terminal.

### Como criar uma stream

```
List<String> nomes = new ArrayLIst<>();
nomes.add("Chicó");
nomes.add("Vicentao");
nomes.add("Cabo Setenta");
Stream<String> stream = nomes.stream();
```

### Operações intermediárias
**Filter**: O método filter() é usado para filtrar elementos de uma stream de acordo com uma condição (predicado). Para isso, ele recebe como parâmetro um objeto que implementa a interface Predicate<T> (interface funcional que define uma função com valor de retorno igual a um boolean) e retorna uma nova stream contendo apenas os elementos que satisfazem à condição.

**Map**: O método map() permite realizar transformações em uma lista de dados sem a necessidade de variáveis intermediárias, apenas utilizando como argumento uma função do tipo java.util.function.Function, que, assim como Predicate<T>, também é uma interface funcional. Essa função toma cada elemento de uma stream como parâmetro e retorna o elemento processado como resposta.

**Sorted**: O método sorted() retorna uma nova stream contendo os elementos da stream original ordenados de acordo com algum critério.

**Distinct**: A operação distinct() retorna uma stream contendo apenas elementos que não se repetem, de acordo com a implementação do método equals().

**Limit**:  limit() é utilizado para limitar o número de elementos em uma stream.

### Operações terminais
**ForEach**: O método forEach() é usado para sobre uma stream e executar algum processamento como um loop for.

**Average**: average() permite calcular a média dos valores dos elementos (usado nas implementações de Stream par tipos primitivos como LongStream ou DoubleStream).

**Reduce**: O método reduce() é uma operação de redução que pode ser usada para transformar todos os elementos de uma stream em um único valor, com base em um predicado. Usado para somar, multiplicar ou encontrar o máximo e o mínimo de uma coleção.

**Collect**: Com o método collect() é possível coletar os elementos de uma stream em coleções, convertendo a stream para uma List, Set ou Map.

**Count**: O método count() retorna a quantidade de elementos em uma stream.

**AllMatch**: O método allMatch() verifica se todos os elementos de uma stream atendem a um critério passado como parâmetro, através de um Predicate, e retorna um valor booleano. 

## Referência
[Java 8: Iniciando o desenvolvimento com a Streams API | Oracle Brasil](https://www.oracle.com/br/technical-resources/articles/java-stream-api.html)