kni*_*ttl 6 java group-by having java-stream collectors
Java (9+) 流是否支持HAVING类似于 SQL的子句?用例:分组然后删除具有特定计数的所有组。是否可以将以下 SQL 子句编写为 Java 流?
GROUP BY id
HAVING COUNT(*) > 5
Run Code Online (Sandbox Code Playgroud)
我能想到的最接近的是:
input.stream()
.collect(groupingBy(x -> x.id()))
.entrySet()
.stream()
.filter(entry -> entry.getValue().size() > 5)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
Run Code Online (Sandbox Code Playgroud)
但是提取分组结果的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;
}));
Run Code Online (Sandbox Code Playgroud)
由于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;
});
}
Run Code Online (Sandbox Code Playgroud)
为了获得更高的灵活性,这个收集器允许一个任意的映射生成收集器,但由于这不强制映射类型,它会在之后通过简单地使用复制构造函数来强制一个可变映射。在实践中,这不会发生,因为默认是使用HashMap. 当调用者显式请求 aLinkedHashMap来维护订单时,它也可以工作。我们甚至可以通过将行更改为
if(!(m instanceof HashMap || m instanceof TreeMap
|| m instanceof EnumMap || m instanceof ConcurrentMap)) {
m = new HashMap<>(m);
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,没有标准的方法来确定地图是否可变。
自定义收集器现在可以很好地用作
Map<KeyType, List<ElementType>> result =
input.stream()
.collect(having(groupingBy(x -> x.id()), (key,list) -> list.size() > 5));
Run Code Online (Sandbox Code Playgroud)