如何将Stream<Map<String, Map<String, String>>>合并为一个Map<String, Map<String, String>>?

3

我正在尝试将一个 Stream<Map<String, Map<String, String>>> 对象与所有 Streams 中的键合并为一个单一的映射。

例如,

final Map<String, someOtherObjectToProcess> someObject;

final List<Map<String, Map<String, String>>> list = someObject.entrySet()
            .stream()
            .flatMap(this::getInfoStream)
            .collect(Collectors.toList());

getInfoStream的签名为

public Stream<Map<String, Map<String, String>>> getInfoStream(Map.Entry<String, someOtherObjectToProcess> entry)

如果我使用(Collectors.toList()),我就能够获取这些Map对象的列表。
如果我使用上述代码,样例输出如下:
[{
    "name" : {
        "property":"value"
    }
},

{
    "name2" : {
        "property":"value"
    }
}]

但是我想将其收集到一个具有以下结构的 Map 中
{
    "name" : {
        "property":"value"
    },
    "name2" : {
        "property":"value"
    }
}

只要键是唯一的,就可以通过Collectors.toMap()或其他替代方法来实现这一点。

1
请展示getInfoStream的签名以及someObject的样子。 - Andrew Tobilko
1
由于某种原因,问题的原因似乎是getInfoStream的实现,它返回一个Stream<Map<>>,甚至不是Stream<Map.Entry> - Naman
4个回答

5
当你遇到

Stream<Map<String, Map<String, String>>> stream = ...

(我假设这是.flatMap(this::getInfoStream)的结果),你可以调用

.flatMap(map -> map.entrySet().stream())

从所有映射中创建一个条目流,该流将生成Stream<Map.Entry<String,Map<String,String>>>。现在,您只需要从该流中收集每个条目的键和值到映射中。假设每个键在所有地图中都是唯一的,则可以使用以下方式:
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

但如果键不唯一,则需要决定将为同一键放置什么值于新映射中。我们可以通过填充...部分来实现。

.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (vOld, vNew) -> ...));
//                                                                                ^^^

其中 vOld 保存着结果映射表中相同键下当前的值,而 vNew 则保存着新值(来自当前流“迭代”)。
例如,如果你想要忽略新值,你可以简单地返回旧值/当前值,使用 (vOld,vNew) -> vOld

因此,简而言之(假设键是唯一的):

Map<String, Map<String, String>> combinedMap = 
        /*your Stream<Map<String, Map<String, String>>>*/
        .flatMap(map -> map.entrySet().stream())
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

在我的情况下,地图的键是唯一的并更新了问题。 - Anirudh Mergu

1

解决这个问题的另一种方法是不使用collector(toList()),而是使用另一个重载的.collect()方法,该方法需要提供Supplier、Accumulator和Combiner:

Stream<Map<String, Map<String, String>>> stream = ...
Map<String, Map<String, String>> result = stream
       .collect(HashMap::new, HashMap::putAll, HashMap::putAll);

1
在我看来,最易读的方法是将所有内容映射到一个Map.Entry,然后使用Collectors::toMap将所有内容收集回一个Map中。
import static java.util.stream.Collectors.toMap;

// ...

someObject.entrySet()
          .stream()
          .flatMap(this::getInfoStream)
          .flatMap(map -> map.entrySet().stream())
          .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (one, two) -> one));

(one, two) -> one 是合并函数,如果有重复的内容,就随意选择第一个出现的。


1

TL;DR:

var merged = Stream.of(map1, map2, ..., mapN).reduce(new HashMap<>(), (a, b) -> {
    a.putAll(b);
    return a;
});

你可以使用reduce将一系列Map<String, Map<String, String>>元素合并为一个:
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {
        alternative1();
        alternative2();
    }

    // Use reduce without an initial identity value
    public static void alternative1() {
        Map<String, Map<String, String>> m1 = new HashMap<>();
        m1.put("name", Map.of("property", "value"));

        Map<String, Map<String, String>> m2 = new HashMap<>();
        m2.put("name2", Map.of("property", "value"));

        Stream<Map<String, Map<String, String>>> mapStream = Stream.of(m1, m2);

        Map<String, Map<String, String>> m3 = mapStream.reduce((a, b) -> {
            Map<String, Map<String, String>> temp = new HashMap<>();
            temp.putAll(a);
            temp.putAll(b);
            return temp;
        }).orElseThrow();

        System.out.println(m3);
    }

    // Use reduce with an initial empty map as the identity value
    public static void alternative2() {
        Map<String, Map<String, String>> m1 = new HashMap<>();
        m1.put("name", Map.of("property", "value"));

        Map<String, Map<String, String>> m2 = new HashMap<>();
        m2.put("name2", Map.of("property", "value"));

        Stream<Map<String, Map<String, String>>> mapStream = Stream.of(m1, m2);

        Map<String, Map<String, String>> m3 = mapStream.reduce(new HashMap<>(), (a, b) -> {
            a.putAll(b);
            return a;
        });

        System.out.println(m3);
    }
}

输出:

{name={property=value}, name2={property=value}}
{name={property=value}, name2={property=value}}

但要注意,这些解决方案假设键(namename2)是唯一的,否则重复的键会使映射条目互相覆盖。


使用更现代的语法相同的逻辑:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {
        alternative1();
        alternative2();
    }

    // Use reduce without an initial identity value
    public static void alternative1() {
        var m1 = Map.of("name", Map.of("property", "value"));
        var m2 = Map.of("name2", Map.of("property", "value"));
        var m3 = Stream.of(m1, m2).reduce((a, b) -> {
            var temp = new HashMap<String, Map<String, String>>();
            temp.putAll(a);
            temp.putAll(b);
            return temp;
        }).orElseThrow();

        System.out.println(m3);
    }

    // Use reduce with an initial empty map as the identity value
    public static void alternative2() {
        var m1 = Map.of("name", Map.of("property", "value"));
        var m2 = Map.of("name2", Map.of("property", "value"));
        var m3 = Stream.of(m1, m2).reduce(new HashMap<>(), (a, b) -> {
            a.putAll(b);
            return a;
        });

        System.out.println(m3);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接