使用Java 8 Lambda Expression合并Map流

时间:2015-07-18 06:08:09

标签: java lambda java-8 java-stream

我是lambda表达的新手,并试图在我的任务中使用它。

问题:

我有两个类型为m1的地图m2Map<Integer, String>,必须合并到一个地图中 Map<Integer, List<String>>,其中两个地图中相同键的值都被收集到一个列表中并放入一个新的地图中。

基于我探索的解决方案:

Map<Integer, List<String>> collated = 
                Stream.concat(m1.entrySet().stream(), m2.entrySet().stream()).collect(
                        Collectors.toMap(Entry::getKey,
                                Entry::getValue, (a, b) -> {
                                    List<String> merged = new ArrayList<String>(a);
                                    merged.addAll(b);
                                    return merged;
                                }));

但是,此解决方案期望源ListMap<Integer, List<String>>,因为toMap中的合并函数需要操作数和结果具有相同的类型。

我不想更改源集合。请提供您使用lambda表达式实现此目的的输入。

5 个答案:

答案 0 :(得分:21)

这是groupingBy收藏家的工作:

Stream.of(m1,m2)
        .flatMap(m->m.entrySet().stream())
        .collect(groupingBy(
                Map.Entry::getKey,
                mapping(Map.Entry::getValue, toList())
        ));

答案 1 :(得分:11)

我现在无法测试它,但我认为你需要它来改变从Entry :: getValue到包含该值的List的值的映射:

Map<Integer, List<String>> collated = 
    Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
          .collect(Collectors.toMap(Entry::getKey,
                                    e -> {
                                          List<String> v = new ArrayList<String>();
                                          v.add(e.getValue());
                                          return v;
                                         },
                                    (a, b) -> {
                                         List<String> merged = new ArrayList<String>(a);
                                         merged.addAll(b);
                                         return merged;
                                    }));
编辑:这个想法是正确的。语法不是。当前语法有效,但有点难看。必须有一个更短的方式来编写它。

您也可以将e -> {..}替换为e -> new ArrayList<String>(Arrays.asList(new String[]{e.getValue()}))

e -> Stream.of(e.getValue()).collect(Collectors.toList())

编辑:

或者您可以使用groupingBy

执行此操作
Map<Integer, List<String>> collated = 
    Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
          .collect(Collectors.groupingBy(Map.Entry::getKey,
                                         Collectors.mapping(Map.Entry::getValue,
                                                            Collectors.toList())));

答案 2 :(得分:3)

如果您需要纯Java-8解决方案,Misha的解决方案是最好的。如果您不介意使用第三方库,那么使用我的StreamEx会稍微缩短一点。

Map<Integer, List<String>> map = StreamEx.of(m1, m2)
        .flatMapToEntry(Function.identity())
        .grouping();

在内部,它与Misha的解决方案相同,只是语法糖。

答案 3 :(得分:3)

这似乎是使用番石榴Multimap的绝佳机会。

ListMultimap<Integer, String> collated = ArrayListMultimap.create();
collated.putAll(Multimaps.forMap(m1));
collated.putAll(Multimaps.forMap(m2));

如果你真的需要Map<Integer, List<String>>

Map<Integer, List<String>> mapCollated = Multimaps.asMap(collated);

答案 4 :(得分:0)

为了清楚起见,您可以使用带有三个参数的 Collectors.toMap 方法。

Map<Integer, String> m1 = Map.of(1, "A", 2, "B");
Map<Integer, String> m2 = Map.of(1, "C", 2, "D");
// two maps into one
Map<Integer, List<String>> m3 = Stream
        .of(m1.entrySet(), m2.entrySet())
        .flatMap(Collection::stream)
        .collect(Collectors.toMap(
                // key - Integer
                e -> e.getKey(),
                // value - List<String>
                e -> List.of(e.getValue()),
                // mergeFunction - two lists into one
                (list1, list2) -> {
                    List<String> list = new ArrayList<>();
                    list.addAll(list1);
                    list.addAll(list2);
                    return list;
                }));
// output
System.out.println(m3); // {1=[A, C], 2=[B, D]}