Java 8 Stream:使用多个收集器进行分组 [英] Java 8 Stream: groupingBy with multiple Collectors
问题描述
我想通过一个分类器使用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屋!