Java 8 Stream:使用多个收集器进行分组 [英] Java 8 Stream: groupingBy with multiple Collectors

查看:661
本文介绍了Java 8 Stream:使用多个收集器进行分组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想通过一个分类器使用Java 8 Stream和Group,但是有多个Collector函数。因此,在分组时,例如计算一个字段(或可能是另一个字段)的平均值和总和。

I want to use a Java 8 Stream and Group by one classifier but have multiple Collector functions. So when grouping, for example the average and the sum of one field (or maybe another field) is calculated.

我尝试用一​​个例子来简化这一点: / p>

I try to simplify this a bit with an example:

public void test() {
    List<Person> persons = new ArrayList<>();
    persons.add(new Person("Person One", 1, 18));
    persons.add(new Person("Person Two", 1, 20));
    persons.add(new Person("Person Three", 1, 30));
    persons.add(new Person("Person Four", 2, 30));
    persons.add(new Person("Person Five", 2, 29));
    persons.add(new Person("Person Six", 3, 18));

    Map<Integer, Data> result = persons.stream().collect(
            groupingBy(person -> person.group, multiCollector)
    );
}

class Person {
    String name;
    int group;
    int age;

    // Contructor, getter and setter
}

class Data {
    long average;
    long sum;

    public Data(long average, long sum) {
        this.average = average;
        this.sum = sum;
    }

    // Getter and setter
}

结果应该是一个与分组结果相关联的地图,如

The result should be a Map that associates the result of grouping like

1 => Data(average(18, 20, 30), sum(18, 20, 30))
2 => Data(average(30, 29), sum(30, 29))
3 => ....

这对于像Collectors.counting()这样的函数完全没问题但是我喜欢链接多个(理想情况下从列表中无限)。

This works perfectly fine with one function like "Collectors.counting()" but I like to chain more than one (ideally infinite from a List).

List<Collector<Person, ?, ?>>

是否可以做这样的事情?

Is it possible to do something like this?

推荐答案

对于求和和求平均的具体问题,请使用 collectAndThen summarizingDouble

For the concrete problem of summing and averaging, use collectingAndThen along with summarizingDouble:

Map<Integer, Data> result = persons.stream().collect(
        groupingBy(Person::getGroup, 
                collectingAndThen(summarizingDouble(Person::getAge), 
                        dss -> new Data((long)dss.getAverage(), (long)dss.getSum()))));

对于更通用的问题(收集关于你人员的各种事情),你可以创建一个像这个:

For the more generic problem (collect various things about your Persons), you can create a complex collector like this:

// Individual collectors are defined here
List<Collector<Person, ?, ?>> collectors = Arrays.asList(
        Collectors.averagingInt(Person::getAge),
        Collectors.summingInt(Person::getAge));

@SuppressWarnings("unchecked")
Collector<Person, List<Object>, List<Object>> complexCollector = Collector.of(
    () -> collectors.stream().map(Collector::supplier)
        .map(Supplier::get).collect(toList()),
    (list, e) -> IntStream.range(0, collectors.size()).forEach(
        i -> ((BiConsumer<Object, Person>) collectors.get(i).accumulator()).accept(list.get(i), e)),
    (l1, l2) -> {
        IntStream.range(0, collectors.size()).forEach(
            i -> l1.set(i, ((BinaryOperator<Object>) collectors.get(i).combiner()).apply(l1.get(i), l2.get(i))));
        return l1;
    },
    list -> {
        IntStream.range(0, collectors.size()).forEach(
            i -> list.set(i, ((Function<Object, Object>)collectors.get(i).finisher()).apply(list.get(i))));
        return list;
    });

Map<Integer, List<Object>> result = persons.stream().collect(
        groupingBy(Person::getGroup, complexCollector)); 

地图值是列表,其中第一个元素是应用第一个收集器的结果,依此类推。您可以使用 Collectors.collectingAndThen(complexCollector,list - > ...)添加自定义修整器步骤,以将此列表转换为更合适的名称。

Map values are lists where first element is the result of applying the first collector and so on. You can add a custom finisher step using Collectors.collectingAndThen(complexCollector, list -> ...) to convert this list to something more appropriate.

这篇关于Java 8 Stream:使用多个收集器进行分组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆