将多个`Collectors :: groupBy`函数与Java Streams

时间:2018-05-28 10:05:49

标签: java java-stream collectors

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

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

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

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

现在,有了这样的方法列表并列出了一些东西,我想把这些事情分组给吸气鬼。

我的意思是,例如,对于列表[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)

但这种方法不起作用。如何实现我想到的结果?

2 个答案:

答案 0 :(得分:0)

你可以这样做:

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

答案 1 :(得分:0)

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

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 s ...

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

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

等条目