将groupingBy用于嵌套Map,但收集到不同类型的对象

时间:2017-05-17 22:28:57

标签: java java-8 java-stream guava

所以我有这个“有效”的代码(为了简单起见,取代了一些名称):

 Map<String, Map<String, ImmutableList<SomeClassA>>> someMap =
      someListOfClassA.stream()
      .filter(...)
      .collect(Collectors.groupingBy(SomeClassA::someCriteriaA,
            Collectors.groupingBy(SomeClassA::someCriteriaB, GuavaCollectors.toImmutableList()
            )
      ));

但是,我想更改此代码,以便在通过SomeClassA字段进行分组后,内部集合是SomeClassB。例如,如果类看起来像这样:

假设它们都具有所有args构造函数

class SomeClassA { 
    String someCriteriaA;
    String someCriteriaB;
    T someData;
    String someId;
}

class SomeClassB {
    T someData;
    String someId; 
}

在某处有一种方法:

public static Collection<SomeClassB> getSomeClassBsFromSomeClassA(SomeClassA someA) {
    List<Some List of Class B> listOfB = someMethod(someA);
    return listOfB; // calls something using someClassA, gets a list of SomeClassB 
}

我想将生成的SomeClass Bs列表展平为

Map<String, Map<String, ImmutableList<SomeClassB>>> someMap = 
    someListOfClassA.stream()
    .filter(...)
    . // not sure how to group by SomeClassA fields but result in a list of SomeClassB since one SomeClassA can result in multiple SomeClassB

我不确定这如何适合上面的代码。如何将基于SomeClassB的一堆列表收集到SomeClassA的所有值的单个列表中?如果单个ClassA映射到单个ClassB,我知道如何使用Collectors.mapping使它工作,但由于每个ClassA导致多个ClassB,我不知道如何让它工作。

任何想法都将不胜感激。谢谢!

2 个答案:

答案 0 :(得分:4)

使用这样的自定义收藏夹:

Map<String, Map<String, List<SomeClassB>>> someMap =
                someListOfClassA.stream()
                        .filter(...)
                        .collect(Collectors.groupingBy(SomeClassA::getSomeCriteriaA,
                                Collectors.groupingBy(SomeClassA::getSomeCriteriaB,
                                        Collectors.mapping(a -> getSomeClassBsFromSomeClassA(a),
                                                flatMapToImmutableList()))));

你可以实现你所追求的目标:

X, Y = np.meshgrid(X, Y)

答案 1 :(得分:3)

虽然我们都在等待Java 9的Collectors.flatMapping(感谢@shmosel的链接),但您可以编写自己的收藏夹来实现您的目标:

public static <T, D, R> Collector<T, ?, R> flatMapping(
        Function<? super T, ? extends Stream<? extends D>> streamMapper,
        Collector<? super D, ?, R> downstream) {

    class Acc {
        Stream.Builder<Stream<? extends D>> builder = Stream.builder();

        void add(T t) {
            builder.accept(streamMapper.apply(t));
        }

        Acc combine(Acc another) {
            another.builder.build().forEach(builder);
            return this;
        }

        R finish() {
            return builder.build()
                    .flatMap(Function.identity()) // Here!
                    .collect(downstream);
        }
    }
    return Collector.of(Acc::new, Acc::add, Acc::combine, Acc::finish);
}

此帮助程序方法使用Collector.of和本地类Acc来累积由提供的streamMapper函数返回的流,该函数将原始流的元素作为参数。这些流累积在Stream.Builder中,将在应用收集器的修整器功能时构建。

在构建这个流流之后,它立即与身份函数进行平面映射,因为我们只想连接流。 (我本可以使用流列表而不是流的流,但我认为Stream.Builder非常高效且使用率很低。

Acc还实现了一个组合器方法,该方法将给定的Acc流的流合并到此Acc的流构建器中。仅当原始流并行时才会使用此功能。

以下是您在示例中使用此方法的方法:

Map<String, Map<String, ImmutableList<SomeClassB>>> map = someListOfClassA.stream()
    .filter(...)
    .collect(
        Collectors.groupingBy(SomeClassA::getSomeCriteriaA,
            Collectors.groupingBy(SomeClassA::getSomeCriteriaB,
                flatMapping(
                    a -> getSomeClassBsFromSomeClassA(a).stream(),
                    ImmutableList.toImmutableList()))));

编辑:由于@Holger在下面的评论中指出,在累积时不需要将数据缓冲到流构建器中。相反,可以通过在累加器函数中执行展平权来实现平面映射收集器。 Here is @Holger's own implementation of such collector,我在此同意后逐字复制:

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> acc = downstream.accumulator();
    return Collector.of(downstream.supplier(),
            (a, t) -> {
                try (Stream<? extends U> s = mapper.apply(t)) {
                    if (s != null) s.forEachOrdered(u -> acc.accept(a, u));
                }
            },
            downstream.combiner(), downstream.finisher(),
            downstream.characteristics().toArray(new Collector.Characteristics[0]));
}