Java Streams GroupingBy和按计数过滤(类似于SQL的HAVING)

问题描述 投票:7回答:3

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调用基本上是将映射映射到其自身。

[我看到有collectingAndThenfiltering收集器,但是我不知道它们是否可以解决我的问题(或者更确切地说,如何正确应用它们)。

是否有上述的更好(更惯用的)版本,还是我坚持收集到中间图,将其过滤然后收集到最终图上?

java group-by java-stream having collectors
3个回答
7
投票

一般来说,必须在分组后执行该操作,因为在确定一个组是否满足条件之前,您需要完全收集一个组。

不是将地图收集到另一个相似的地图中,而是可以使用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));

2
投票

我知道的唯一方法是将Collectors.collectingAndThenCollectors.collectingAndThen函数中的相同实现一起使用:

finisher

0
投票

如果您想获得更具可读性的代码,也可以使用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    
© www.soinside.com 2019 - 2024. All rights reserved.