用Java 8合并两张Map

评论 0 浏览 0 2018-10-06

1.绪论

在这个快速教程中,我们将演示如何使用Java 8的功能来合并两个Map

更具体地说,我们将研究不同的合并情况,包括有重复条目的Map。

2.初始化

开始时,我们将定义两个Map实例:

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

Employee类看起来像这样:

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

然后,我们可以将一些数据推送到Map实例中:

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);

请注意,我们的Map中的employee1employee5条目有相同的键,我们将在后面使用。

3. Map.merge()

Java 8在java.util.Map接口中增加了一个新的merge()函数。

merge()函数的工作原理如下;如果指定的键没有与一个值相关联,或者该值是空的,它就将该键与给定的值相关联。

否则,它将用给定的重映射函数的结果替换该值。如果重映射函数的结果是空的,它将删除该结果。

首先,我们将通过复制map1中的所有条目来构建一个新的HashMap

Map<String, Employee> map3 = new HashMap<>(map1);

接下来,我们将介绍merge()函数,以及一个合并的规则。

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

最后,我们将遍历map2,并将这些条目合并到map3

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

让我们运行该程序,并打印map3的内容。

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

结果是,我们合并的Map拥有之前HashMap条目的所有元素。键值重复的条目已被合并为一个条目

另外,我们可以看到,最后一个条目的Employee对象具有来自map1id,并且该值是从map2中挑选出来的。

这是因为我们在合并函数中定义了一个规则。

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

4. Stream.concat()

Java 8中的Stream API也可以为我们的问题提供一个简单的解决方案。首先,我们需要把我们的Map实例合并成一个Stream。这正是Stream.concat()操作的作用。

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

在这里,我们把Map条目集作为参数传给了对方。

接下来,我们需要将我们的结果收集到一个新的Map。为此,我们可以使用Collectors.toMap()

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

因此,收集器将使用我们Map的现有键和值。但这个解决方案远非完美。一旦我们的收集器遇到键值重复的条目,它就会抛出一个IllegalStateException

为了处理这个问题,我们可以简单地将第三个“合并”lambda参数添加到我们的收集器中。

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

每次检测到重复的键时,它都会使用lambda表达式。

最后,我们将把这一切都放在一起。

Map<String, Employee> 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())));

现在,让我们运行代码,看看结果。

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

我们可以看到,键值为“Henry”的重复条目被合并成一个新的键值对,其中新的Employee的id是从map2中挑选出来的,而值则是从map1中挑选的。

5. Stream.of()

为了继续使用Stream API,我们可以在Stream.of()的帮助下,将我们的Map实例变成一个统一的流。

在这里,我们不需要创建一个额外的集合来处理流的问题。

Map<String, Employee> 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())));

首先,我们将map1map2转化为一个流。接下来,我们将流转换为Map。我们可以看到,toMap()的最后一个参数是一个合并函数。它通过从v1条目中挑选id字段,并从v2中挑选名称,解决了重复键的问题。

这是运行该程序后打印的map3实例:

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.简单的Stream

此外,我们可以使用stream() pipeline来组合我们的Map条目。下面的代码片段演示了如何通过忽略重复的条目来添加来自map2map1的条目:

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

正如我们所期望的那样,合并后的结果是:

{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

除了JDK提供的解决方案外,我们还可以使用流行的StreamEx库。

简单地说,StreamEx是对StreamAPI的增强,提供了许多额外的有用方法。我们将使用一个EntryStream实例来操作键值对

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

我们的想法是将我们的Map流合并成一个。然后,我们将把这些条目收集到新的map3实例中。提到(e1, e2) -> e1表达式也很重要,因为它有助于定义处理重复键的规则。没有它,我们的代码将抛出一个IllegalStateException

而现在,结果出来了:

{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.摘要

在这篇简短的文章中,我们学习了Java 8中合并Map的不同方法。更具体地说,我们使用了Map.merge()、Stream API、StreamEx

一如既往,本文中所使用的代码可以在GitHub上找到。

最后更新2023-01-19
0 个评论
标签