用Java 8合并两张Map
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中的employee1和employee5条目有相同的键,我们将在后面使用。
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对象具有来自map1的id,并且该值是从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())));
首先,我们将map1和map2转化为一个流。接下来,我们将流转换为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条目。下面的代码片段演示了如何通过忽略重复的条目来添加来自map2和map1的条目:
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上找到。