Mesclando dois mapas com o Java 8

Mesclando dois mapas com o Java 8

 

1. Introdução

Neste tutorial rápido,we’ll demonstrate how to merge two maps using the Java 8 capabilities.

Para ser mais específico, examinaremos diferentes cenários de fusão, incluindo mapas com entradas duplicadas.

2. Inicialização

Para começar, vamos definir duas instâncias deMap:

private static Map map1 = new HashMap<>();
private static Map map2 = new HashMap<>();

A classeEmployee se parece com isto:

public class Employee {
 
    private Long id;
    private String name;
 
    // constructor, getters, setters
}

Então, podemos enviar alguns dados para as instâncias deMap:

Employee employee1 = new Employee(1L, "Henry");
map1.put(employee1.getName(), employee1);
Employee employee2 = new Employee(22L, "Annie");
map1.put(employee2.getName(), employee2);
Employee employee3 = new Employee(8L, "John");
map1.put(employee3.getName(), employee3);

Employee employee4 = new Employee(2L, "George");
map2.put(employee4.getName(), employee4);
Employee employee5 = new Employee(3L, "Henry");
map2.put(employee5.getName(), employee5);

Observe que temos chaves idênticas para as entradasemployee1 eemployee5 em nossos mapas, que usaremos mais tarde.

3. Map.merge()

Java 8 adds a new merge() function into the java.util.Map interface.

Veja como funciona a funçãomerge(): Se a chave especificada ainda não estiver associada a um valor ou se o valor for nulo, ela associa a chave ao valor fornecido.

Caso contrário, ele substitui o valor pelos resultados da função de remapeamento fornecida. Se o resultado da função de remapeamento for nulo, ele removerá o resultado.

Primeiro, vamos construir um novoHashMap copiando todas as entradas demap1:

Map map3 = new HashMap<>(map1);

A seguir, vamos apresentar a funçãomerge() junto com a regra de mesclagem:

map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())

Finalmente, vamos iterar sobremap2e mesclar as entradas emmap3:

map2.forEach(
  (key, value) -> map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())));

Vamos executar o programa e imprimir o conteúdo demap3:

John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
George=Employee{id=2, name='George'}
Henry=Employee{id=1, name='Henry'}

Como resultado,our combined Map has all the elements of the previous HashMap entries. Entries with duplicate keys have been merged into one entry.

Além disso, notamos que o objetoEmployee da última entrada tem oid demap1, e o valor é selecionado demap2.

Isso ocorre devido à regra que definimos em nossa função de fusão:

(v1, v2) -> new Employee(v1.getId(), v2.getName())

4. Stream.concat()

A APIStream em Java 8 também pode fornecer uma solução fácil para nosso problema. Primeiro,we need to combine our Map instances into one Stream. Isso é exatamente o que a operaçãoStream.concat() faz:

Stream combined = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream());

Aqui passamos os conjuntos de entradas do mapa como parâmetros. Next, we need to collect our result into a new Map. Para isso, podemos usarCollectors.toMap():

Map result = combined.collect(
  Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Como resultado, o coletor usará as chaves e valores existentes de nossos mapas. Mas esta solução está longe de ser perfeita. Assim que nosso coletor encontrar entradas com chaves duplicadas, ele lançará umIllegalStateException.

Para lidar com esse problema, simplesmente adicionamos um terceiro parâmetro lambda "fusão" em nosso coletor:

(value1, value2) -> new Employee(value2.getId(), value1.getName())

Ele usará a expressão lambda toda vez que uma chave duplicada for detectada.

Finalmente, reunindo todos:

Map result = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
  .collect(Collectors.toMap(
    Map.Entry::getKey,
    Map.Entry::getValue,
    (value1, value2) -> new Employee(value2.getId(), value1.getName())));

Finalmente, vamos executar o código e ver os resultados:

George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
Henry=Employee{id=3, name='Henry'}

Como vemos, as entradas duplicadas com a chave“Henry” foram mescladas em um novo par de valores-chave ondethe id of the new Employee was picked from the map2 and the value from map1.

5. Stream.of()

Para continuar a usar a APIStream, podemos transformar nossas instânciasMap em um fluxo unificado com a ajuda deStream.of().

Aqui, não precisamos criar uma coleção adicional para trabalhar com os fluxos:

Map map3 = Stream.of(map1, map2)
  .flatMap(map -> map.entrySet().stream())
  .collect(Collectors.toMap(
    Map.Entry::getKey,
    Map.Entry::getValue,
    (v1, v2) -> new Employee(v1.getId(), v2.getName())));

Primeiro,we transform map1 and map2 into a single stream. Em seguida, convertemos o fluxo no mapa. Como podemos ver, o último argumento detoMap() é uma função de fusão. Ele resolve o problema de chaves duplicadas escolhendo o campo id da entradav1, e o nome dev2.

A instância impressamap3 após a execução do programa:

George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
Henry=Employee{id=1, name='Henry'}

6. Streaming Simples

Além disso, podemos usar um spipelinestream() para montar nossas entradas de mapa. O trecho de código abaixo demonstra como adicionar as entradas demap2emap1 ignorando as entradas duplicadas:

Map map3 = map2.entrySet()
  .stream()
  .collect(Collectors.toMap(
    Map.Entry::getKey,
    Map.Entry::getValue,
    (v1, v2) -> new Employee(v1.getId(), v2.getName()),
  () -> new HashMap<>(map1)));

Como esperamos, os resultados após a mesclagem são:

{John=Employee{id=8, name='John'},
Annie=Employee{id=22, name='Annie'},
George=Employee{id=2, name='George'},
Henry=Employee{id=1, name='Henry'}}

7. StreamEx

Além das soluções fornecidas pelo JDK, também podemos usar a popular bibliotecaStreamEx .

Simplificando,StreamEx is an enhancement for the Stream APIe fornece muitos métodos úteis adicionais. Usaremos umEntryStream instance to operate on key-value pairs:

Map map3 = EntryStream.of(map1)
  .append(EntryStream.of(map2))
  .toMap((e1, e2) -> e1);

A idéia é mesclar os fluxos de nossos mapas em um. Em seguida, coletamos as entradas na nova instânciamap3. É importante mencionar a expressão(e1, e2) → e1, pois ajuda a definir a regra para lidar com as chaves duplicadas. Sem ele, nosso código lançará umIllegalStateException.

E agora, os resultados:

{George=Employee{id=2, name='George'},
John=Employee{id=8, name='John'},
Annie=Employee{id=22, name='Annie'},
Henry=Employee{id=1, name='Henry'}}

8. Sumário

Neste breve artigo, aprendemos diferentes maneiras de mesclar mapas no Java 8. Mais especificamente,we used Map.merge(), Stream API, StreamEx library.

Como sempre, o código usado durante a discussão pode ser encontradoover on GitHub.