Java 8 Nested (Multi level) group by

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39130122/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 04:06:44  来源:igfitidea点击:

Java 8 Nested (Multi level) group by

javacollectionslambdajava-8java-stream

提问by Sandesh Kumar

I have few classes like below

我有几个像下面这样的课程

class Pojo {
    List<Item> items;
}

class Item {
    T key1;
    List<SubItem> subItems;
}

class SubItem {
    V key2;
    Object otherAttribute1;
}

I want to aggregate the items based on key1and for each aggregation, subitems should be aggregated by key2in following way:

我想根据key1每个聚合来聚合项目,子项目应该通过key2以下方式聚合:

Map<T, Map<V, List<Subitem>>

How is this possible with Java 8 Collectors.groupingBynesting?

Java 8Collectors.groupingBy嵌套怎么可能做到这一点?

I was trying something and stuck halfway at

我正在尝试一些东西并卡在了一半

pojo.getItems()
    .stream()
    .collect(
        Collectors.groupingBy(Item::getKey1, /* How to group by here SubItem::getKey2*/)
    );

Note: This not same as cascaded groupingBywhich does multilevel aggregation based on fields in the same object as discussed here

注:这不一样的级联groupingBy,其不基于在同一个对象的字段多聚集的讨论在这里

回答by Holger

You can't group a single item by multiple keys, unless you accept the item to potentially appear in multiple groups. In that case, you want to perform a kind of flatMapoperation.

您不能通过多个键对单个项目进行分组,除非您接受该项目可能出现在多个组中。在这种情况下,您想要执行某种flatMap操作。

One way to achieve this, is to use Stream.flatMapwith a temporary pair holding the combinations of Itemand SubItembefore collecting. Due to the absence of a standard pair type, a typical solution is to use Map.Entryfor that:

实现此目的的一种方法是使用Stream.flatMap一个临时对来保存ItemSubItem收集之前的组合。由于没有标准对类型,典型的解决方案是使用Map.Entry

Map<T, Map<V, List<SubItem>>> result = pojo.getItems().stream()
    .flatMap(item -> item.subItems.stream()
        .map(sub -> new AbstractMap.SimpleImmutableEntry<>(item, sub)))
    .collect(Collectors.groupingBy(e -> e.getKey().getKey1(),
                Collectors.mapping(Map.Entry::getValue,
                    Collectors.groupingBy(SubItem::getKey2))));

An alternative not requiring these temporary objects would be performing the flatMapoperation right in the collector, but unfortunately, flatMappingwon't be there until Java?9.

另一种不需要这些临时对象的方法是flatMap在收集器中执行操作,但不幸的flatMapping是,直到 Java?9 才会出现。

With that, the solution would look like

有了这个,解决方案看起来像

Map<T, Map<V, List<SubItem>>> result = pojo.getItems().stream()
    .collect(Collectors.groupingBy(Item::getKey1,
                Collectors.flatMapping(item -> item.getSubItems().stream(),
                    Collectors.groupingBy(SubItem::getKey2))));

and if we don't want to wait for Java?9 for that, we may add a similar collector to our code base, as it's not so hard to implement:

如果我们不想等待 Java?9,我们可以在我们的代码库中添加一个类似的收集器,因为它并不难实现:

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