1. 概述
在这个教程中,我们将探讨将Map.Entry
对象流收集到LinkedHashMap
的不同方法。
一个LinkedHashMap
类似于HashMap
,但不同之处在于它维护了插入顺序。
2. 理解问题
我们可以通过调用Map
的entrySet()
方法,然后使用stream()
方法来获取映射条目的流。这个流使我们能够处理每个条目。处理是通过中间操作完成的,可以使用filter()
方法进行过滤,或使用map()
方法进行转换。最终,我们必须通过适当的终止操作决定对流做什么。 在我们的例子中,我们面临的挑战是将流收集到LinkedHashMap
中。
假设我们有以下用于本教程的地图:
Map<Integer, String> map = Map.of(1, "value 1", 2, "value 2");
我们将流化并收集地图条目到LinkedHashMap
中,目标是满足以下断言:
assertThat(result)
.isExactlyInstanceOf(LinkedHashMap.class)
.containsOnly(entry(1, "value 1"), entry(2, "value 2"));
3. 使用Collectors.toMap()
方法
我们可以使用Collectors.toMap()
方法的一个重载,将流收集到我们选择的映射中:
static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction, Supplier<M> mapFactory)
因此,我们将此收集器作为流的终止collect()
操作的一部分:
map
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> {throw new RuntimeException();}, LinkedHashMap::new));
为了保留每个条目的键值对,我们使用Map.Entry::getKey
和Map.Entry::getValue
方法引用作为keyMapper
和valueMapper
函数。mergeFunction
允许我们处理具有相同键的条目的任何冲突。因此,当我们抛出一个RuntimeException
时,因为我们的情况不应该有任何冲突。最后,我们使用LinkedHashMap
构造函数引用作为mapFactory
,以提供条目将被收集到的映射。
需要注意的是,我们也可以使用其他toMap()
重载方法来实现目标。但是,这些方法缺少mapFactory
参数,所以流默认收集到HashMap
中。因此,我们可以使用LinkedHashMap
的构造函数将HashMap
转换为我们想要的类型:
new LinkedHashMap<>(map
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
然而,这会导致创建两个映射实例来达到我们的目标,因此最初的方案更优。
4. 使用Collectors.groupingBy()
方法
我们可以使用Collectors.groupingBy()
方法的一个重载,指定群组收集到的映射:
static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M>
groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory,
Collector<? super T, A, D> downstream)
假设我们有一个现有的城市-国家条目映射:
Map<String, String> cityToCountry = Map.of("Paris", "France", "Nice", "France", "Madrid", "Spain");
但是,我们想根据国家分组城市。因此,我们使用groupingBy()
与collect()
方法:
Map<String, Set<String>> countryToCities = cityToCountry
.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue, LinkedHashMap::new, Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
assertThat(countryToCities)
.isExactlyInstanceOf(LinkedHashMap.class)
.containsOnly(entry("France", Set.of("Paris", "Nice")), entry("Spain", Set.of("Madrid")));
我们使用Map.Entry::getValue
方法引用作为classifier
函数来进行国家分组。我们使用LinkedHashMap::new
作为mapFactory
来声明我们想要收集群组到的映射。 最后,我们使用Collectors.mapping()
方法作为下游收集器,从我们的条目中提取键,以便收集到每个集合中。
5. 使用put()
方法
我们可以使用终止forEach()
操作和put()
方法将流收集到现有LinkedHashMap
中:
Map<Integer, String> result = new LinkedHashMap<>();
map
.entrySet()
.stream()
.forEach(entry -> result.put(entry.getKey(), entry.getValue()));
或者,我们可以避免流化,直接使用Set
对象的forEach()
方法:
map
.entrySet()
.forEach(entry -> result.put(entry.getKey(), entry.getValue()));
进一步简化,我们可以在映射上直接使用forEach()
:
map.forEach((k, v) -> result.put(k, v));
然而,我们应该注意,这些方法都引入了副作用操作到我们的函数式编程中,因为它们修改了现有的映射。 因此,使用更命令式的风格更为合适:
for (Map.Entry<Integer, String> entry : map.entrySet()) {
result.put(entry.getKey(), entry.getValue());
}
我们使用增强的for
循环迭代并添加每个条目的键值到现有的LinkedHashMap
中。
6. 使用LinkedHashMap
的构造函数
如果我们只想简单地将映射转换为LinkedHashMap
,则无需流化条目来做这件事。我们可以直接使用LinkedHashMap
的构造函数进行转换:
new LinkedHashMap<>(map);
7. 总结
在这篇文章中,我们探讨了将Map.Entry
对象流收集到LinkedHashMap
的各种方法。我们研究了不同的终止操作以及不使用流化的替代方法,以达成目标。
如往常一样,本文中的代码示例可在GitHub上找到。