我遇到了一个问题,那就是如何正确地组合多个Collectors::groupingBy
函数,然后将它们同时应用于给定的输入。
假设我有一些类实现了以下接口:
interface Something {
String get1();
String get2();
String get3();
String get4();
}
现在我可以从这个接口获得一些方法组合的列表,即这些列表可以是:[Something::get1, Something::get3]
,[Something::get2, Something::get1, Something::get3]
。
现在,有了这样一个方法列表和一些东西的列表,我想通过getter对这些东西进行分组。
我的意思是,例如,对于list [Something::get1, Something::get3]
和list [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)
但这种方法行不通。怎样才能达到我想要的结果?
发布于 2018-05-28 18:45:34
您可以这样做:
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);
因为您知道向收集器传递了多少结果,所以可以将其转换为适当的Map
groupingBy
。在本例中,应用了两个groupingBy
:
Map<String, Map<String, List<Something>>> mapResult = (Map<String, Map<String, List<Something>>>) result
发布于 2018-05-28 20:04:50
因为您不知道列表中有多少个函数,所以不能声明一个反映嵌套的编译时类型。但是,即使使用一个产生未知结果类型的收集器类型,用你想要的干净的函数方式组合它也是不可行的。你能得到的最接近的是
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))
这样的条目
https://stackoverflow.com/questions/50563866
复制相似问题