简体   繁体   English

使用 IntSummaryStatistics 跨多个字段求平均值

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM