繁体   English   中英

将多个 `Collectors::groupBy` 函数与 Java Streams 结合使用

[英]Combine multiple `Collectors::groupBy` functions with Java Streams

我在正确组合多个Collectors::groupingBy函数然后一次将它们全部应用于给定输入时遇到问题。

假设我有一些实现以下接口的类:

interface Something {
    String get1();
    String get2();
    String get3();
    String get4();
}

现在我可以从这个接口获得一些方法组合的列表,即这些列表可以是: [Something::get1, Something::get3] , [Something::get2, Something::get1, Something::get3] .

现在,有了这样的方法列表和一些东西的列表,我想通过 getter 对这些东西进行分组。

我的意思是,例如,对于列表[Something::get1, Something::get3]和一个列表[Something1, Something2, ...]我想通过先分组出头的列表get1通过,然后get2

这可以通过以下方式实现:

var lst = List.of(smth1, smth2, smth3);
lst.stream()
   .collect(Collectors.groupingBy(Something::get1, Collectors.groupingBy(Something::get3)))

如果我有任何想要应用于分组的任意方法列表怎么办?

我在想这样的事情(ofc。这不起作用,但你会明白的):

假设List<Function<Something, String>> groupingFunctions是我们想要应用于分组的方法列表。

var collector = groupingFunctions.stream()
                                 .reduce((f1, f2) -> Collectors.groupingBy(f1, Collectors.groupingBy(f2)))

进而

List.of(smth1, smth2, smth3).stream().collect(collector)

但这种方法行不通。 如何达到我想的结果?

你可以这样做:

public static Collector createCollector(Function<A, String>... groupKeys) {
    Collector collector = Collectors.toList();
    for (int i = groupKeys.length - 1; i >= 0; i--) {
        collector = Collectors.groupingBy(groupKeys[i], collector);
    }
    return collector;
}

这为您提供了一个原始收集器,因此分组后的流结果也是原始的。

Collector collector = createCollector(Something::get1, Something::get2);

你可以像这样使用这个collector

Object result = somethingList.stream().collect(collector);

因为您知道传递给收集器的groupingBy ,所以您可以将其转换为适当的Map结果。 在这种情况下,应用了两个groupingBy

Map<String, Map<String, List<Something>>> mapResult = (Map<String, Map<String, List<Something>>>) result

由于您不知道列表中有多少函数,因此您无法声明反映嵌套的编译时类型。 但是,即使使用收集器类型产生一些未知的结果类型,也无法以您想要的干净功能方式进行组合。 你能得到的最接近的是

var collector = groupingFunctions.stream()
    .<Collector<Something,?,?>>reduce(
        Collectors.toList(),
        (c,f) -> Collectors.groupingBy(f, c),
        (c1,c2) -> { throw new UnsupportedOperationException("can't handle that"); });

这有两个基本问题。 没有办法为两个Collector实例提供有效的合并功能,因此虽然这可能适用于顺序操作,但它不是一个干净的解决方案。 此外,结果映射的嵌套顺序将相反; 列表的最后一个函数将提供最外层地图的键。

可能有办法解决这个问题,但所有这些都会使代码变得更加复杂。 将此与直接循环进行比较:

Collector<Something,?,?> collector = Collectors.toList();
for(var i = groupingFunctions.listIterator(groupingFunctions.size()); i.hasPrevious(); )
    collector = Collectors.groupingBy(i.previous(), collector);

你可以像这样使用收集器

Object o = lst.stream().collect(collector);

但是需要instanceof和类型转换来处理Map ......

使用反映分组功能的List键创建单个非嵌套Map会更清晰:

Map<List<String>,List<Something>> map = lst.stream().collect(Collectors.groupingBy(
    o -> groupingFunctions.stream().map(f -> f.apply(o))
                          .collect(Collectors.toUnmodifiableList())));

它允许查询像map.get(List.of(arguments, matching, grouping, functions))这样的条目

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM