如何使用Java Streams API合并Map列表和Lists值?

时间:2015-07-27 09:50:16

标签: java java-8 java-stream

如何通过X.p减少OnCreate分组并同时加入所有列表值,以便最后得到Map<X, List<String>>

这是我到目前为止所尝试的:

Map<Integer, List<String>>

但结果是:z = {123 = [E,F,A,B,C,D],124 = [E,F,A,B,C,D]}。

我希望z = {123 = [A,B,C,D],124 = [E,F]}

4 个答案:

答案 0 :(得分:5)

以下是使用两个Stream流水线的一种方法:

Map<Integer, List<String>> z = 
// first process the entries of the original Map and produce a 
// Map<Integer,List<List<String>>>
    x.entrySet()
     .stream()
     .collect(Collectors.groupingBy(entry -> entry.getKey().p, 
                                    mapping(Map.Entry::getValue,
                                            toList())))
// then process the entries of the intermediate Map and produce a 
// Map<Integer,List<String>>
     .entrySet()
     .stream()
     .collect (toMap (Map.Entry::getKey,
                      e -> e.getValue()
                            .stream()
                            .flatMap(List::stream)
                            .collect(toList())));

Java 9应该添加一个flatMapping收集器,这将使您的生活更轻松(感谢Holger,我了解了这个新功能)。

输出:

z={123=[A, B, C, D], 124=[E, F]}

答案 1 :(得分:3)

通过编写自己的收集器,有一种方法可以在一次运行中实现:

Map<Integer, List<String>> z = x.entrySet().stream().collect(
  Collectors.groupingBy(entry -> entry.getKey().p,
    Collectors.mapping(Entry::getValue, 
      Collector.of(ArrayList::new, (a, b) -> a.addAll(b), (a, b) -> {
        a.addAll(b);
        return a;
      })
    )
  )
);

答案 2 :(得分:3)

使用我的EntryStream库的StreamEx类,可以很容易地解决这些任务:

Map<Integer, List<String>> z = EntryStream.of(x)
           .mapKeys(k -> k.p)
           .flatMapValues(List::stream)
           .grouping();

在内部,它变成了这样的东西:

Map<Integer, List<String>> z = x.entrySet().stream()
        .map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey().p, e.getValue()))
        .<Entry<Integer, String>>flatMap(e -> e.getValue().stream()
            .map(s -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), s)))
        .collect(Collectors.groupingBy(e -> e.getKey(), 
            Collectors.mapping(e -> e.getValue(), Collectors.toList())));

所以它实际上是一个单一的流管道。

如果您不想使用第三方代码,可以稍微简化上述版本:

Map<Integer, List<String>> z = x.entrySet().stream()
        .<Entry<Integer, String>>flatMap(e -> e.getValue().stream()
                .map(s -> new AbstractMap.SimpleEntry<>(e.getKey().p, s)))
        .collect(Collectors.groupingBy(e -> e.getKey(), 
                Collectors.mapping(e -> e.getValue(), Collectors.toList())));

虽然它看起来仍然很难看。

最后请注意,在JDK9中有一个名为flatMapping的新标准收集器,它可以通过以下方式实现:

public static <T, U, A, R>
Collector<T, ?, R> flatMapping(Function<? super T, ? extends Stream<? extends U>> mapper,
                               Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return Collector.of(downstream.supplier(),
            (r, t) -> {
                try (Stream<? extends U> result = mapper.apply(t)) {
                    if (result != null)
                        result.sequential().forEach(u -> downstreamAccumulator.accept(r, u));
                }
            },
            downstream.combiner(), downstream.finisher(),
            downstream.characteristics().toArray(new Collector.Characteristics[0]));
}

使用此收集器,您的任务可以更简单地解决,无需其他库:

Map<Integer, List<String>> z = x.entrySet().stream()
        .map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey().p, e.getValue()))
        .collect(Collectors.groupingBy(e -> e.getKey(), 
                flatMapping(e -> e.getValue().stream(), Collectors.toList())));

答案 3 :(得分:3)

您错误地使用了reducing收藏家。第一个参数必须是还原操作的标识值。但是你要通过向它添加值来修改它,这完美地解释了结果:所有值都被添加到相同的ArrayList,这应该是不变的标识值。

您要做的是Mutable reductionCollectors.reducing不适合。{您可以使用Collector.of(…)方法创建合适的收集器:

Map<Integer, List<String>> z = x.entrySet().stream().collect(groupingBy(
    entry -> entry.getKey().p, Collector.of(
        ArrayList::new, (l,e)->l.addAll(e.getValue()), (a,b)->{a.addAll(b);return a;})));