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);
注意,我们在 map1
和 map2
中都包含了键为 "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 获取。