1. 概述

在这个教程中,我们将探讨将Map.Entry对象流收集到LinkedHashMap的不同方法。

一个LinkedHashMap类似于HashMap,但不同之处在于它维护了插入顺序。

2. 理解问题

我们可以通过调用MapentrySet()方法,然后使用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::getKeyMap.Entry::getValue方法引用作为keyMappervalueMapper函数。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上找到。