Java 8 Stream:groupingBy 与多个收集器多个、收集器、Java、Stream

由网友(╰★Say丶残狼)分享简介:我想使用 Java 8 Stream 并按一个分类器分组,但有多个收集器函数.因此,在分组时,例如计算一个字段(或可能是另一个字段)的平均值和总和.I want to use a Java 8 Stream and Group by one classifier but have multiple Collector...

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

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.

我试着用一个例子来简化一下:

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
}

结果应该是关联分组结果的Map

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?

推荐答案

求和求平均的具体问题,使用collectingAndThen 以及 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()))));

对于更一般的问题(收集有关您的 Persons 的各种信息),您可以像这样创建一个复杂的收集器:

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.

阅读全文

相关推荐

最新文章