Quase todos os dias precisamos lidar com transformações de dados em aplicações Java, dessa forma, a partir do Java 8 na API de Stream, esses processos de transformações se tornaram mais fluentes com os conceitos de programação funcional, onde as coleções do Java receberam o método .stream que inicia fluxo de operações nos elementos visando conversões, reduções, ordenações, entre outros.
Na sequência será demonstrado alguns códigos no contexto de transformações de coleções para Map.
Exemplo 1: Transformar uma lista de empresas em um Map de cnpj por empresa
Sem stream: é preciso declarar um map e iterar a lista de empresas inserindo o cnpj como chave e a empresa como valor.
final Map<String, Empresa> empresasPorCnpj = new HashMap(); for(Empresa empresa : empresas) { empresasPorCnpj.put(empresa.getCnpj(), empresa); }
Com Stream: as coleções tem o método stream() que inicia um fluxo sobre a coleção e a função toMap cria um map a partir dos dados iterados no fluxos.
import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; Map<String, Empresa> empresasPorCnpj = empresas.stream() .collect(Collectors.toMap(Conta::getCnpj, Function.identity()));
- stream(): Inicia o fluxo sobre a coleção;
- .collect: É uma função do stream que espera um retorno;
- Collectors.toMap: Transforma o valor do stream em uma map, passando chave e valor por parâmetro;
- Conta::getCnpj: Retorna o valor do cnpj via method reference;
- Function.identity(): É uma função identidade, que retorna ela mesmo, ou seja, retorna o valor do objeto iterando no strem, empresa./li>
Exemplo 2: Agrupar uma lista de contas por data
Sem stream: novamente é preciso criar um map e iterar a lista de contas porém é preciso validar se já existe contas para a data, senão é preciso inicializar a lista.
final Map<LocalDate, List<Conta>> contasPorData = new HashMap<>(); for(Conta conta : contas) { Lis<Cont> contasDaData = contasPorData.get(conta.getData()); if(contasDaData == null) { contasDaData = new ArrayList<>(); } contasPorData.put(conta.getData(), contasDaData); }
Sem stream: é similar ao exemplo anterior, só que o stream tem uma outra função, Collectors.groupingBy, que já retorna uma lista da entidade agrupada pelo atributo passado por parâmetro.
import java.util.function.Function; import java.util.stream.Collectors; Map<LocalDate, Lis<Conta> contasPorData = contas.stream() .collect(Collectors.groupingBy(Conta::getData));
- Collectors.groupingBy: Agrupa os dados iterados na stream, retornando um map do valor referenciado pela lista de objetos do stream, exemplo: Map<ValorReferenciado, List
- Conta::getData: Retorna o valor do data via method reference.
Dentro dessas funções citadas (Collectors.groupingBy, Collectors.toMap) há a possibilidade de aplicar novas funções para transformar ainda mais os resultados durante o fluxo do stream.
Exemplo 3 – Agrupar as contas por data e retornar um map do identificador pelo valor da conta, ao invés de toda entidade conta, dessa forma, após o agrupamento é necessário uma transformação de conta para um map identificador por valor.
Na API de stream do Java 8, isso é possível usando funções dentro de funções, ou seja, dentro da função de groupingBy é aplicado um toMap no resultado do agrupamento, demonstrado na sintaxe abaixo:
Map<LocalDate, Map<String, BigDecimal>> valorDaContaPorIdentificadorPorData = contas.stream() .collect(Collectors.groupingBy(Conta::getData, Collectors.toMap(Conta::getIdentificador, Conta::getValorTotal)));
A leitura desse stream seria: Agrupar as contas por data (Collectors.groupingBy(Conta::getData) e com o resultado desse agrupado, transformar as contas em um item do map, onde a chave é o identificador e valor é valor total da conta (Collectors.toMap(Conta::getIdentificador, Conta::getValorTotal)).
Exemplo 4 – Somar o valor total das contas por data.
Similar ao exemplo anterior, onde é aplicado uma função sobre um resultado agrupado, com um novo conceito de redução, que “reduz” uma lista de contas em um valor somado de todos os totais das contas.
Map<LocalDate, BigDecimal> valorTotalPorData = contas.stream() .collect(Collectors.groupingBy(Conta::getData, Collectors.reducing(BigDecimal.ZERO, Conta::getValorTotal, BigDecimal::add)));
- Collectors.reducing: Reduz uma coleção para um valor;
- BigDecimal.ZERO (Parâmetro 1a do reducing): Valor informado caso não haver dados no stream;
- Conta::getValorTotal (Parâmetro 2a do reducing): Valor a ser reduzido;
- BigDecimal::add (Parâmetro 3a do reducing): Operador para executar a redução.