Java 8 Stream:使用多个收集器进行分组

Phi*_*ppS 11 java java-8 java-stream

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

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

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
}
Run Code Online (Sandbox Code Playgroud)

结果应该是一个将分组结果关联起来的Map

1 => Data(average(18, 20, 30), sum(18, 20, 30))
2 => Data(average(30, 29), sum(30, 29))
3 => ....
Run Code Online (Sandbox Code Playgroud)

这与"Collectors.counting()"之类的函数完美配合,但我喜欢链接多个(理想情况下是List中的无限).

List<Collector<Person, ?, ?>>
Run Code Online (Sandbox Code Playgroud)

可以这样做吗?

Tag*_*eev 16

对于求和平均的具体问题,请collectingAndThen连同summarizingDouble:

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

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

// 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)); 
Run Code Online (Sandbox Code Playgroud)

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