Transformando collections em Map com Stream no Java 8

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.
Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

w

Conectando a %s