Java >> Java tutorial >  >> Tag >> SQL

Java Streams GroupingBy og filtrering efter antal (svarende til SQL's HAVING)

Operationen skal udføres efter grupperingen generelt, da du skal samle en gruppe fuldt ud, før du kan afgøre, om den opfylder kriterierne.

I stedet for at samle et kort til et andet lignende kort, kan du bruge removeIf for at fjerne ikke-matchende grupper fra resultatkortet og injicere denne efterbehandling i opsamleren:

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

Siden groupingBy(Function) collector giver ingen garantier vedrørende mutabiliteten af ​​det oprettede kort, vi skal specificere en leverandør til et mutable map, hvilket kræver, at vi er eksplicitte om downstream-samleren, da der ikke er nogen overbelastet groupingBy for kun at angive funktion og kortleverandør.

Hvis dette er en tilbagevendende opgave, kan vi lave en brugerdefineret samler, der forbedrer koden ved hjælp af den:

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

For større fleksibilitet tillader denne samler en vilkårlig kortproducerende samler, men da dette ikke håndhæver en korttype, vil den håndhæve et foranderligt kort bagefter ved blot at bruge kopikonstruktøren. I praksis vil dette ikke ske, da standarden er at bruge en HashMap . Det fungerer også, når den, der ringer, eksplicit anmoder om en LinkedHashMap at opretholde ordenen. Vi kunne endda støtte flere sager ved at ændre linjen til

if(!(m instanceof HashMap || m instanceof TreeMap
  || m instanceof EnumMap || m instanceof ConcurrentMap)) {
    m = new HashMap<>(m);
}

Desværre er der ingen standardmetode til at bestemme, om et kort kan ændres.

Den brugerdefinerede opsamler kan nu fint bruges som

Map<KeyType, List<ElementType>> result =
    input.stream()
        .collect(having(groupingBy(x -> x.id()), (key,list) -> list.size() > 5));

Den eneste måde, jeg er klar over, er at bruge Collectors.collectingAndThen med den samme implementering i finisher funktion:

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 tag