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

注意,我们在 map1map2 中都包含了键为 "Henry" 的条目,这将在后续演示中用于处理键冲突。

3. 使用 Map.merge() 方法

Java 8 引入了新的 merge() 方法java.util.Map 接口中。

该方法的工作逻辑如下:

  • 如果指定的键尚未与任何值关联,或其值为 null,则将该键与给定值进行关联。
  • 否则,它会使用给定的重映射函数的结果替换当前值。如果结果为 null,则删除该键。

我们可以先复制 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'}

📌 可以看到,所有元素都被合并进来了,重复键的条目也根据我们定义的合并函数进行了处理

合并函数如下:

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

即保留第一个对象的 id,第二个对象的 name

4. 使用 Stream.concat() 方法

另一个简单粗暴的方式是借助 Java 8 的 Stream API。

首先将两个 Map 的 entry 集合合并成一个流:

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

再通过 Collectors.toMap() 收集成一个新的 Map:

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

⚠️ 但是这种方式有个坑:如果遇到重复键,会抛出 IllegalStateException

为了解决这个问题,我们需要提供一个合并函数:

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

最终完整代码如下:

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'}

📌 **可以看到,重复键的条目被合并,且 id 来自 map2,name 来自 map1**。

5. 使用 Stream.of() 方法

我们还可以使用 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())));

📌 这种方式的优势在于代码更简洁,无需手动拼接流。

运行后输出:

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. 使用简单的流式处理

我们也可以只对 map2 的 entry 进行流式处理,并将其合并到 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)));

📌 第四个参数是 mapSupplier,用于指定目标 Map 的类型。

运行结果如下:

{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 提供了更丰富的流式操作 API,其中 EntryStream 可以直接处理键值对:

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

📌 toMap() 的合并函数 (e1, e2) -> e1 表示遇到重复键时优先保留第一个值。

运行结果如下:

{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.concat()
  • 使用 Stream.of()
  • 使用简单的流式处理
  • 使用第三方库 StreamEx

📌 每种方式都有其适用场景,合理选择可以让你的代码更简洁高效

本文代码示例可从 GitHub 获取。


原始标题:Merging Two Maps with Java | Baeldung