List Stream em Java

Daniel Christofolli
4 min readDec 29, 2018

--

Stream é uma API que veio para facilitar a vida dos desenvolvedores, reduzindo a quantidade de linhas e facilitando a leitura dos códigos.

Podemos modificar um objeto da classe List de diversas formas, usando a stream.

Para exemplificar alguns modos de uso, eu criei uma classe chamada “Pessoa”

public class Pessoa {
String nome;
int idade;

@Override
public String toString() {
return "nome='" + nome + '\'' +
", idade=" + idade;
}

public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}

A classe ListStream, que tem o método main

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ListStream {
public static void main(String[] args) {
List<Pessoa> pessoa = Arrays.asList
(new Pessoa("Daniel", 35),
(new Pessoa("Daiane", 34)),
(new Pessoa("Karina", 28)),
(new Pessoa("Marcelo", 30)));
Stream<Pessoa> streamPessoa = pessoa.Stream();
}
}

Eu criei uma lista “pessoa” do tipo “Pessoa” com 4 objetos, e abaixo um objeto Stream recebendo essa lista. Uma forma mais eficiente de fazer isso, seria substituir o .stream por .parallelStream, que permite o uso de fluxos em paralelo, aproveitando de uma maneira mais eficiente o poder de processamento das máquinas atuais.

Agora, alguns exemplos de manipulação dessa lista, usando stream:

Count

Esse método apenas retorna a quantidade de elementos.

FindAny

O método findAny retorna qualquer valor da lista.

Map

O método map é usado para mapear cada elemento da lista de acordo com o que é pedido no código.

No código abaixo, vamos usar o map, retornando apenas as idades das pessoas que estão na nossa lista.

System.out.println(streamPessoa.map(p-> p.getIdade()));

A saída vai ser algo do tipo:

java.util.stream.ReferencePipeline$3@d041cf

Isso acontece porque map é uma operação intermediária e retorna um stream. Para conseguirmos exibir o retorno corretamente, vamos precisar usar o método collect(Collectors.toList()), que é uma operação de terminal que normalmente está presente no final de uma operação intermediária e finaliza o seu fluxo, transformando-o em uma lista.

System.out.println(streamPessoa.map(p-> p.getIdade()).collect(Collectors.toList()));

Assim, teremos a seguinte saída:

[35, 34, 28, 30]

MapToInt, mapToDouble, mapToLong

E se quisermos a soma das idades das pessoas que os nomes começam com a letra “D”? Para fazer operações com o resultado do map, nesse caso onde só queremos a soma de dois inteiros, precisaremos usar o mapToInt, como no exemplo abaixo:

System.out.println(streamPessoa.filter(p-> p.getNome().startsWith("D")).mapToInt(p-> p.getIdade()).sum());

Com a saída:

69

MapToDouble é usado quando o sistema precisar retornar um número decimal. MapToLong, quando precisarmos de um número muito grande.

Filter

Podemos também, filtrar uma lista, utilizando o método filter, conforme o exemplo abaixo:

System.out.println(streamPessoa.filter(p-> p.getNome().startsWith("D")).collect(Collectors.toList()));

Nesse exemplo, eu pedi que o Java procurasse todas as pessoas que o nome começasse com a letra “D”, e me retornasse os dados em uma lista. A saída foi a seguinte:

[nome=’Daniel’, idade=35, nome=’Daiane’, idade=34]

Max e min

São métodos que retornam o valor máximo e o mínimo, respectivamente.

No exemplo abaixo, o sistema vai comparar as idades e retornar a pessoa mais velha:

System.out.println(streamPessoa.max(Comparator.comparing(i -> i.getIdade())));

[nome=’Daniel’, idade=35]

Agora, a pessoa mais nova:

System.out.println(streamPessoa.min(Comparator.comparing(i -> i.getIdade())));

[nome=’Karina’, idade=28]

IntSummaryStatistics

Quando tentamos fazer mais que uma operação matemática usando a mesma Stream, ao executarmos o código, teremos uma exceção como a seguinte:

Exception in thread “main” java.lang.IllegalStateException: stream has already been operated upon or closed

Isso acontece por já termos usado uma instância da classe Stream em uma operação matemática, e tentarmos usá-la novamente em outra operação matemática. Agora, estamos entendendo um pouco da forma como o Stream gerencia suas operações as quais são de dois tipos: Intermediárias e Terminais.

As operações Intermediárias são aquelas que retornam um novo Stream para que novas operações intermediárias sejam realizadas de maneira fluente (encadeadas). Utilizamos esse tipo de operações quando executamos os métodos filter() e depois mapToInt(), por exemplo.

As operações Terminais são operações que juntam os resultados de um Stream e retornam um valor ou um objeto. Depois de realizada uma operação terminal, o mesmo Stream não poderá ser usado por outras operações intermediárias ou executar novas operações terminais. Exemplos de operações terminais são: sum(), min(), max(), findFirst(), dentre outras. Uma maneira fácil de identificar uma operação terminal é observar o retorno dos métodos, uma operação terminal nunca retorna uma interface Stream.

Para trabalharmos somente com números, nesse exemplo que estamos usando, vamos criar uma variável do tipo IntSummaryStatistics, recebendo o Stream streamPessoa já mapeando por nome, que fica assim:

public class ListStream {
public static void main(String[] args) {
List<Pessoa> pessoa = Arrays.asList(new Pessoa("Daniel", 35),
(new Pessoa("Daiane", 34)),
(new Pessoa("Karina", 28)),
(new Pessoa("Marcelo", 30)));
Stream<Pessoa> streamPessoa = pessoa.parallelStream();
IntSummaryStatistics estatistica = streamPessoa.mapToInt(p -> p.getIdade()).summaryStatistics();

Desse jeito, poderemos trabalhar com os números mais facilmente.

Se nesse ponto, imprimirmos a variável estatística, teremos o resultado:

IntSummaryStatistics{count=4, sum=127, min=28, average=31,750000, max=35}

Podemos notar que recebemos informações importantes, como quantidade de elementos, a soma de todas as idades, o valor mínimo, a média e o valor máximo.

Mas podemos limitar o resultado que queremos receber, simplesmente usando os sufixos .getCount(), .getSum(), .getMin(), getMax() e getAverage().

Então…

Podemos concluir que o uso do Stream deixa o código mais bonito e elegante, mais fácil de ser lido, diminui a quantidade de linhas e ainda podemos otimizar o uso dos processadores, usando o parallelStream.

--

--