[英]Averaging across multiple fields with IntSummaryStatistics
I'm trying to use Java 8 streams to create a single CarData object, which consists of an average of all the CarData fields in the list coming from getCars
;我正在尝试使用 Java 8 流来创建一个 CarData 对象,该对象由来自getCars
的列表中所有 CarData 字段的平均值组成;
CarData = new CarData();
CarData.getBodyWeight returns Integer
CarData.getShellWeight returns Integer
List<CarData> carData = carResults.getCars();
IntSummaryStatistics averageBodyWeight = carData.stream()
.mapToInt((x) -> x.getBodyWeight())
.summaryStatistics();
averageBodyWeight.getAverage();
IntSummaryStatistics averageShellWeight = carData.stream()
.mapToInt((x) -> x.getShellWeight())
.summaryStatistics();
getShellWeight.getAverage();
I don't want to have to put each of these back together in my final returned result.我不想在我最终返回的结果中将这些重新组合在一起。
Visually, this is my list视觉上,这是我的清单
getCars() : [
{CarData: { getBodyWeight=10, getShellWeight=3 } }
{CarData: { getBodyWeight=6, getShellWeight=5 } }
{CarData: { getBodyWeight=8, getShellWeight=19 } }
]
and the output I'm trying to achieve is a single object that has the average of each of the fields I specify.我试图实现的输出是一个对象,它具有我指定的每个字段的平均值。 not sure If I need to use Collectors.averagingInt
or some combo of IntSummaryStatistics to achieve this.不确定我是否需要使用Collectors.averagingInt
或 IntSummaryStatistics 的一些组合来实现这一点。 Easy to do across one field for either of these techniques, just not sure what I'm missing when using multiple integer fields.对于这些技术中的任何一种,在一个字段中都可以轻松完成,只是不确定在使用多个整数字段时我缺少什么。
{CarData: { getBodyWeight=8, getShellWeight=9 } }
Starting with JDK 12, you can use the following solution:从 JDK 12 开始,您可以使用以下解决方案:
CarData average = carData.stream().collect(Collectors.teeing(
Collectors.averagingInt(CarData::getBodyWeight),
Collectors.averagingInt(CarData::getShellWeight),
(avgBody, avgShell) -> new CarData(avgBody.intValue(), avgShell.intValue())));
For older Java versions, you can do either, add the teeing
implementation of this answer to your code base and use it exactly as above or create a custom collector tailored to your task, as shown in Andreas' answer .对于较旧的 Java 版本,您可以执行以下任一操作,将这个答案的teeing
实现添加到您的代码库中并完全按照上述方式使用它,或者创建一个为您的任务量身定制的自定义收集器,如Andreas 的答案所示。
Or consider that streaming twice over a List
in memory is not necessarily worse than doing two operations in one stream, both, readability- and performance-wise.或者考虑在内存中的List
上流式传输两次不一定比在一个流中执行两个操作更糟糕,无论是在可读性还是性能方面。
Note that calling intValue()
on Double
objects has the same behavior as the (int)
casts in Andreas' answer.请注意,在Double
对象上调用intValue()
与 Andreas 的答案中的(int)
转换具有相同的行为。 So in either case, you have to adjust the code if other rounding behavior is intended.因此,无论哪种情况,如果需要其他舍入行为,您都必须调整代码。
Or you consider using a different result object, capable of holding two floating point values for the averages.或者您考虑使用不同的结果对象,能够为平均值保存两个浮点值。
You need to write your own Collector
, something like this:您需要编写自己的Collector
,如下所示:
class CarDataAverage {
public static Collector<CarData, CarDataAverage, Optional<CarData>> get() {
return Collector.of(CarDataAverage::new, CarDataAverage::add,
CarDataAverage::combine,CarDataAverage::finish);
}
private long sumBodyWeight;
private long sumShellWeight;
private int count;
private void add(CarData carData) {
this.sumBodyWeight += carData.getBodyWeight();
this.sumShellWeight += carData.getShellWeight();
this.count++;
}
private CarDataAverage combine(CarDataAverage that) {
this.sumBodyWeight += that.sumBodyWeight;
this.sumShellWeight += that.sumShellWeight;
this.count += that.count;
return this;
}
private Optional<CarData> finish() {
if (this.count == 0)
return Optional.empty();
// adjust as needed if averages should be rounded
return Optional.of(new CarData((int) (this.sumBodyWeight / this.count),
(int) (this.sumShellWeight / this.count)));
}
}
You then use it like this:然后像这样使用它:
List<CarData> list = ...
Optional<CarData> averageCarData = list.stream().collect(CarDataAverage.get());
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.