如何将来自父地图的地图值与 java 8 流相结合

How to combine Map values from parent Map with java 8 stream

我在地图中有一张地图,如下所示:

Map<String, Map<Integer, BigDecimal>> mapInMap; //--> with values like: 
/*
"a": (1: BigDecimal.ONE),
"b": (2: BigDecimal.TEN),
"c": (1: BigDecimal.ZERO)
*/

我想通过预期以下结果来组合内部映射:

Map<Integer, BigDecimal> innerMapCombined; //--> with values:
/*
1: BigDecimal.ZERO,
2: BigDecimal.TEN
*/

这是我预定义组合映射并使用 forEach 的解决方案:

Map<Integer, BigDecimal> combined = new HashMap<>();
mapInMap.forEach((str, innerMap) -> {
  innerMap.forEach(combined::putIfAbsent);
});

但这会忽略 (1: BigDecimal.ZERO)

你能提供一个 java 8 流的 1 行解决方案吗?

您的问题在于,一旦您初始化地图,并在内部地图上添加 重复键 ,您将重写这些键,因为 那些地图不接受重复的键。因此,你需要先改变这个:

Map<String, Map<Integer, BigDecimal>> mapInMap;

Map允许重复键,例如Multimap 来自Google 番石榴:

Map<String, Multimap<Integer, BigDecimal>> mapInMap = new HashMap<>();

内部地图是这样创建的:

 Multimap<Integer, BigDecimal> x1 = ArrayListMultimap.create();
 x1.put(1, BigDecimal.ONE);
 mapInMap.put("a", x1);

只有 现在您可以尝试使用 Java 8 Streams API 来解决您的问题。例如:

Map<Integer, BigDecimal> map = multiMap.values()
                                       .stream()
                                       .flatMap(map -> map.entries().stream())
                                       .collect(Collectors.toMap(Map.Entry::getKey,
                                                                 Map.Entry::getValue, 
                                                                 (v1, v2) -> v2));

使用toMap方法的mergeFunction参数解决重复键冲突。我们明确表示在重复的情况下取第二个值 (v1, v2) -> v2

问题:

要解决您当前的解决方案不起作用的原因,是因为 Map#putIfAbsent 方法仅在地图中添加并且不替换地图中已经存在的值。


使用for-each的解决方案:

Map#put 是一种方法,但它的局限性在于您无法决定是始终保留此类键的第一个值、计算一个新值还是始终使用最后一个值。出于这个原因,我建议使用 Map#computeIfPresentMap#putIfAbsent 的组合,或者更好的方法,即 Map#merge(K, V, BiFunction)BiFunction remappingFunction:

remappingFunction - the function to recompute a value if present

Map<Integer, BigDecimal> resultMap = new HashMap<>();
for (Map<Integer, BigDecimal> map: mapInMap.values()) {
    for (Map.Entry<Integer, BigDecimal> entry: map.entrySet()) {
         resultMap.merge(entry.getKey(), entry.getValue(), (l, r) -> r);
    }
}

使用流的解决方案API:

要在类似 Stream 的解决方案中重写它,方法是相同的。唯一的区别是 Stream 的声明语法API,但是,思想是非常相似的。

只需 flatMap 结构并使用 Collector.toMap(Function, Function, BinaryOperator 收集到映射,使用 BinaryOperator mergeFunction 合并重复键。

mergeFunction - a merge function, used to resolve collisions between values associated with the same key, as supplied to Map.merge(Object, Object, BiFunction)

Map<Integer, BigDecimal> resultMap = mapInMap.values().stream()
    .flatMap(entries -> entries.entrySet().stream())
    .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (l, r) -> r));

注:@dreamcrash also deserves a credit for his good Stream API 速度方面。


结果:

{1=1, 2=10}是你打印出这样的图的结果(注意BigDecimal是打印成数字的)。此输出符合您的预期输出。

1=BigDecimal.ZERO
2=BigDecimal.TEN

请注意 Map#merge(K, V, BiFunction)Collector.toMap(Function, Function, BinaryOperator 之间的相似之处,它们使用非常相似的方法获得相同的结果。