List Stream em Java
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.