在嵌套 Map 中使用 groupingBy,但收集到不同类型的对象

Using groupingBy into a nested Map, but collecting to a different type of object

所以我有这样的代码 "works"(为简单起见替换了一些名称):

 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 B 的结果列表展平为

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,我不确定如何让它工作。

如有任何想法,我们将不胜感激。谢谢!

使用像这样的自定义收集器:

private static Collector<Collection<SomeClassB>, ?, ImmutableList<SomeClassB>>
        flatMapToImmutableList() {
        return Collectors.collectingAndThen(Collectors.toList(),
                listOfCollectionsOfB ->
                        listOfCollectionsOfB.stream()
                                .flatMap(Collection::stream)
                                .collect(GuavaCollectors.toImmutableList()));
    }

你可以实现你所追求的:

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()))));

虽然我们都在等待 Java 9 的 Collectors.flatMapping(感谢@shmosel 提供 link),但您可以编写自己的收集器来实现您想要的:

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 and a local class Acc to accumulate the streams returned by the provided streamMapper function, which takes an element of the original stream as an argument. These streams are accumulated in a 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 在下面的评论中指出的那样,累积时不需要将数据缓冲到流生成器中。相反,平面映射收集器可以通过在累加器函数中执行展平权来实现。 ,我在他的同意下逐字复制在这里:

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]));
}