使用Java Stream对对象数组按日分组并计算日均平均值
嘿,我来帮你搞定这个问题!你现在分开统计每日的总和和采样数确实有点重复工作,其实完全可以在一次Stream操作里完成这两个计算,直接得到每日的平均值,不用跑两次Stream~
解决方案:一次Stream计算每日平均值
方法1:使用Java 12+的Collectors.teeing(推荐)
Java 12引入的teeing收集器正好适配你的场景——它允许我们在一次分组操作中同时应用两个下游收集器,再把它们的结果合并起来。我们可以用它同时计算每日的平均值总和、采样数量,最后直接相除得到每日平均。
代码示例:
import java.util.Arrays; import java.util.Date; import java.util.Map; import java.util.stream.Collectors; // 先写个可靠的日期分组辅助方法(替代原有的substring,避免toString格式不稳定) private static String getDailyKey(Date date) { return new java.text.SimpleDateFormat("EEE MMM dd").format(date); } // 核心计算逻辑 Map<String, Double> dailyAverages = Arrays.stream(samples) .collect(Collectors.groupingBy( sample -> getDailyKey(sample.getTime()), Collectors.teeing( Collectors.summingDouble(PricingSample::getAverage), // 第一个收集器:统计每日总和 Collectors.counting(), // 第二个收集器:统计每日采样数 (totalSum, sampleCount) -> totalSum / sampleCount // 合并结果:计算平均值 ) ));
方法2:Java 8-11的兼容方案
如果你的项目还在用Java 8到11,没有teeing收集器,也有两种靠谱的方式实现:
方式A:自定义统计对象+分组
先定义一个简单的类来存储每日的总和与计数:
class DailyStats { private double sum; private long count; public void add(double average) { sum += average; count++; } public static DailyStats merge(DailyStats a, DailyStats b) { DailyStats merged = new DailyStats(); merged.sum = a.sum + b.sum; merged.count = a.count + b.count; return merged; } public double getAverage() { return count == 0 ? 0 : sum / count; } }
然后用groupingBy结合reducing收集器来整合数据:
Map<String, Double> dailyAverages = Arrays.stream(samples) .collect(Collectors.groupingBy( sample -> getDailyKey(sample.getTime()), Collectors.collectingAndThen( Collectors.reducing( new DailyStats(), sample -> { DailyStats stats = new DailyStats(); stats.add(sample.getAverage()); return stats; }, DailyStats::merge ), DailyStats::getAverage ) ));
方式B:用数组临时存储总和与计数
如果不想额外定义类,可以用数组来轻量存储两个值:
// 先收集每日的总和[0]与计数[1] Map<String, double[]> tempStats = Arrays.stream(samples) .collect(Collectors.groupingBy( sample -> getDailyKey(sample.getTime()), Collectors.reducing( new double[]{0, 0}, sample -> new double[]{sample.getAverage(), 1}, (a, b) -> new double[]{a[0] + b[0], a[1] + b[1]} ) )); // 转换为最终的平均值Map Map<String, Double> dailyAverages = tempStats.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> entry.getValue()[0] / entry.getValue()[1] ));
小优化:让日期分组更可靠
你原来用e.getTime().toString().substring(0,10)分组,虽然能工作,但Date.toString()的输出依赖系统Locale,可能在不同环境下格式不一致。如果是Java 8+,建议把Date转成LocalDate作为分组key,更类型安全:
import java.time.LocalDate; import java.time.ZoneId; private static LocalDate getLocalDate(Date date) { return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); } // 直接用LocalDate分组 Map<LocalDate, Double> dailyAverages = Arrays.stream(samples) .collect(Collectors.groupingBy( sample -> getLocalDate(sample.getTime()), Collectors.teeing( Collectors.summingDouble(PricingSample::getAverage), Collectors.counting(), (sum, count) -> sum / count ) ));
内容的提问来源于stack exchange,提问作者Matt
相关产品推荐
相关产品推荐

