Colete resultados de uma operação de mapa em um Mapa usando Collectors.toMap ou groupingBy

8

Eu tenho uma lista do tipo List<A>e com a operação de mapa obtendo uma lista coletiva do tipo List<B>para todos os elementos A mesclados em uma lista.

List<A> listofA = [A1, A2, A3, A4, A5, ...]

List<B> listofB = listofA.stream()
  .map(a -> repo.getListofB(a))
  .flatMap(Collection::stream)
  .collect(Collectors.toList());

sem mapa plano

List<List<B>> listOflistofB = listofA.stream()
  .map(a -> repo.getListofB(a))
  .collect(Collectors.toList());

Quero recolher os resultados como um mapa do tipo Map<A, List<B>>e até agora tentando com vários Collectors.toMapou Collectors.groupingByopções, mas não é capaz de obter o resultado desejado.

Amitoj
fonte
2
Você tem Aelementos repetidos no seu List<A>?
Federico Peralta Schaffner

Respostas:

8

Você pode usar o toMapcoletor com uma referência de método limitada para obter o que precisa. Observe também que esta solução pressupõe que você não tenha repetido instâncias A no seu contêiner de origem. Se essa pré-condição se mantiver, esta solução fornecerá o resultado desejado. Aqui está como fica.

Map<A, Collection<B>> resultMap = listofA.stream()
    .collect(Collectors.toMap(Function.identity(), repo::getListofB);

Se você duplicou os elementos A, precisará usar essa função de mesclagem além do que é fornecido acima. A função de mesclagem lida com conflitos de teclas, se houver.

Map<A, Collection<B>> resultMap = listofA.stream()
       .collect(Collectors.toMap(Function.identity(), repo::getListofB, 
            (a, b) -> {
                a.addAll(b);
                return a;
        }));

E aqui está uma abordagem Java9 muito mais sucinta que usa o flatMappingcoletor para manipular elementos A repetidos.

Map<A, List<B>> aToBmap = listofA.stream()
        .collect(Collectors.groupingBy(Function.identity(),
                Collectors.flatMapping(a -> getListofB(a).stream(), 
                        Collectors.toList())));
Ravindra Ranwala
fonte
2
Ei, que bom que você adicionou como lidar com duplicatas! Eu estava adicionando uma resposta com isso, vou deixar porque acho que complementa a sua, felicidades!
Federico Peralta Schaffner
2
Sim, seu comentário foi útil. Boa pegada !
Ravindra Ranwala
3

Seria direto,

listofA.stream().collect(toMap(Function.identity(), a -> getListofB(a)));
Code_Mode
fonte
2

Para coletar as Mapchaves where, os Aobjetos não são alterados e os valores são a lista dos Bobjetos correspondentes , você pode substituir o toList()coletor pelo seguinte coletor:

toMap(Function.identity(), a -> repo.getListOfB(a))

O primeiro argumento define como calcular a chave do objeto original: identity()mantém o objeto original do fluxo inalterado.

O segundo argumento define como o valor é calculado; portanto, aqui apenas consiste em uma chamada para o seu método que transforma a Aem uma lista de B.

Como o repométodo usa apenas um parâmetro, você também pode melhorar a clareza substituindo o lambda por uma referência de método:

toMap(Function.identity(), repo::getListOfB)
kgautron
fonte
Obrigado @kgautron, sua resposta está correta e claramente explicada. Aceitei a outra resposta porque a vi primeiro.
Amitoj 02/10/19
2

Nesta resposta, estou mostrando o que acontece se você repetir Aelementos em sua List<A> listofAlista.

Na verdade, se houvesse duplicatas listofA, o código a seguir geraria um IllegalStateException:

Map<A, Collection<B>> resultMap = listofA.stream()
        .collect(Collectors.toMap(
                            Function.identity(), 
                            repo::getListofB);

A exceção pode ser lançada porque Collectors.toMapnão sabe como mesclar valores quando há uma colisão nas chaves (ou seja, quando a função do mapeador de chaves retorna duplicatas, como seria o caso Function.identity()se houvesse elementos repetidos na listofAlista).

Isso é claramente afirmado nos documentos :

Se as chaves mapeadas contiverem duplicatas (de acordo com Object.equals(Object)), um IllegalStateExceptionserá lançado quando a operação de coleta for executada. Se as chaves mapeadas podem ter duplicatas, use toMap(Function, Function, BinaryOperator).

Os documentos também nos fornecem a solução: caso haja elementos repetidos, precisamos fornecer uma maneira de mesclar valores. Aqui está uma dessas maneiras:

Map<A, Collection<B>> resultMap = listofA.stream()
        .collect(Collectors.toMap(
                            Function.identity(), 
                            a -> new ArrayList<>(repo.getListofB(a)),
                            (left, right) -> {
                                left.addAll(right);
                                return left;
                            });

Isso usa a versão sobrecarregada Collectors.toMapque aceita uma função de mesclagem como seu terceiro argumento. Dentro da função de mesclagem, Collection.addAllestá sendo usado para adicionar os Belementos de cada Aelemento repetido em uma lista unqiue para cada um A.

Na função do mapeador de valores, um novo ArrayListé criado, para que o original List<B>de cada Aum não seja alterado. Além disso, ao criar um Arraylist, sabemos com antecedência que ele pode ser alterado (ou seja, podemos adicionar elementos a ele posteriormente, caso haja duplicatas listofA).

Federico Peralta Schaffner
fonte