【问题标题】:Java 8 Nested (Multi level) group byJava 8 嵌套(多级)分组方式
【发布时间】:2016-08-24 18:01:11
【问题描述】:

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

class Pojo {
    List<Item> items;
}

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

class SubItem {
    V key2;
    Object otherAttribute1;
}

我想根据 key1 聚合项目,对于每个聚合,子项目应按以下方式聚合 key2

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

Java 8 Collectors.groupingBy 嵌套如何实现这一点?

我正在尝试一些东西,但中途卡住了

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

注意:这与级联groupingBy 不同,后者基于与讨论here 相同的对象中的字段进行多级聚合

【问题讨论】:

  • 你可以试试吗?pojo.getItems().stream().collect(Collectors.groupingBy(Item::getKey1)).entrySet().stream() .collect(Collectors.toMap(Entry::getKey, (e)-&gt; e.getValue().stream().map(it -&gt; it.getSubItems().stream().collect(Collectors.groupingBy(SubItem::getKey2)))));我不确定。
  • @David Pérez Cabrera:我认为这行得通,但是将所有内容完全收集到一个临时的 Map&lt;T, List&lt;Item&gt;&gt; 中可能会非常昂贵。

标签: java collections lambda java-8 java-stream


【解决方案1】:

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

实现此目的的一种方法是在收集之前使用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.getKey1(), sub)))
    .collect(Collectors.groupingBy(AbstractMap.SimpleImmutableEntry::getKey,
                Collectors.mapping(Map.Entry::getValue,
                    Collectors.groupingBy(SubItem::getKey2))));

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

这样,解决方案看起来像

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

如果我们不想为此等待 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]));
}

【讨论】:

  • 感谢@Holger,您能否详细说明“接受该项目可能出现在多个组中”。我没有得到这部分。
  • 如果你分组到 Map&lt;T, List&lt;…&gt;&gt;T 键是唯一的,但是当你多组到 Map&lt;T, Map&lt;V, List&lt;…&gt;&gt;&gt; 时,V 键仅在其子映射中是唯一的.但根据您的输入数据结构和实际目标,这可能(可能)不是问题。
  • 在 Map> 的情况下,T 和 V 的每个组合是唯一的吗?如,如果有一个 V1 同时出现在 T1 和 T2 上,T2 的 V1 条目是否会包含 T1 项目?我不确定我是否清楚......
  • @ElSuscriptorJusticiero:不确定我的问题是否正确,您的V1 不是唯一的,但result.get(T1).get(V1)result.get(T2).get(V1) 将返回包含不同项目的不同列表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多