Java 8嵌套(多级)组

San*_*mar 16 java collections lambda java-8 java-stream

我有几个类,如下

class Pojo {
    List<Item> items;
}

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

class SubItem {
    V key2;
    Object otherAttribute1;
}
Run Code Online (Sandbox Code Playgroud)

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

Map<T, Map<V, List<Subitem>>
Run Code Online (Sandbox Code Playgroud)

Java 8 Collectors.groupingBy嵌套有什么可能吗?

我正在尝试一些东西并且中途停留

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

注意:这与级联不同groupingBy,后者根据此处讨论的同一对象中的字段进行多级聚合

Hol*_*ger 21

您不能通过多个键对单个项目进行分组,除非您接受该项目可能出现在多个组中.在这种情况下,您想要执行一种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, sub)))
    .collect(Collectors.groupingBy(e -> e.getKey().getKey1(),
                Collectors.mapping(Map.Entry::getValue,
                    Collectors.groupingBy(SubItem::getKey2))));
Run Code Online (Sandbox Code Playgroud)

不需要这些临时对象的替代方法是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))));
Run Code Online (Sandbox Code Playgroud)

如果我们不想等待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]));
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你分组到一个 `Map&lt;T, List&lt;...&gt;&gt;`,`T` 键是唯一的,但是当你多组到一个 `Map&lt;T, Map&lt;V, List&lt;...&gt;&gt;&gt;` 时, `V` 键仅在其子映射中是唯一的。但是根据您的输入数据结构和实际目标,这可能(很可能)不是问题。 (2认同)