Java(9+)流是否支持类似于SQL的HAVING
子句?用例:分组,然后删除具有特定计数的所有组。是否可以将以下SQL子句编写为Java流?
GROUP BY id
HAVING COUNT(*) > 5
我能想到的最接近的是:
input.stream()
.collect(groupingBy(x -> x.id()))
.entrySet()
.stream()
.filter(entry -> entry.getValue().size() > 5)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
但是提取分组结果的entrySet以收集两次感到很奇怪,尤其是终端collect
调用基本上是将映射映射到其自身。
[我看到有collectingAndThen
和filtering
收集器,但是我不知道它们是否可以解决我的问题(或者更确切地说,如何正确应用它们)。
是否有上述的更好(更惯用的)版本,还是我坚持收集到中间图,将其过滤然后收集到最终图上?
一般来说,必须在分组后执行该操作,因为在确定一个组是否满足条件之前,您需要完全收集一个组。
不是将地图收集到另一个相似的地图中,而是可以使用removeIf
从结果地图中删除不匹配的组,并将此完成操作注入到收集器中:
Map<KeyType, List<ElementType>> result =
input.stream()
.collect(collectingAndThen(groupingBy(x -> x.id(), HashMap::new, toList()),
m -> {
m.values().removeIf(l -> l.size() <= 5);
return m;
}));
由于groupingBy(Function)
收集器不保证所创建映射的可变性,因此我们需要为可变映射指定一个供应商,这需要我们对下游收集器进行明确说明,因为没有重载groupingBy
仅指定功能和地图提供者。
如果这是一项经常性的任务,我们可以使一个自定义收集器使用它来改进代码:
public static <T,K,V> Collector<T,?,Map<K,V>> having(
Collector<T,?,? extends Map<K,V>> c, BiPredicate<K,V> p) {
return collectingAndThen(c, in -> {
Map<K,V> m = in;
if(!(m instanceof HashMap)) m = new HashMap<>(m);
m.entrySet().removeIf(e -> !p.test(e.getKey(), e.getValue()));
return m;
});
}
[为了获得更高的灵活性,此收集器允许使用任意生成地图的收集器,但是由于它不强制执行映射类型,因此之后,只需使用复制构造函数,它将强制执行可变映射。实际上,这种情况不会发生,因为默认设置是使用HashMap
。当调用者显式请求LinkedHashMap
维护订单时,它也起作用。通过将行更改为
if(!(m instanceof HashMap || m instanceof TreeMap
|| m instanceof EnumMap || m instanceof ConcurrentMap)) {
m = new HashMap<>(m);
}
不幸的是,没有确定地图是否可变的标准方法。
自定义收集器现在可以很好地用作
Map<KeyType, List<ElementType>> result =
input.stream()
.collect(having(groupingBy(x -> x.id()), (key,list) -> list.size() > 5));
我知道的唯一方法是将Collectors.collectingAndThen
与Collectors.collectingAndThen
函数中的相同实现一起使用:
finisher
如果您想获得更具可读性的代码,也可以使用Guava Map<Integer, List<Item>> a = input.stream().collect(Collectors.collectingAndThen(
Collectors.groupingBy(Item::id),
map -> map.entrySet().stream()
.filter(e -> e.getValue().size() > 5)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))));
函数(作为重新流的替代方法。)>
[它允许转换地图,有时比Java流提供更短,更易读的语法。
filterValues