Java中用流stream计算加权平均值

💡 原文中文,约5000字,阅读约需12分钟。
📝

内容提要

本文探讨了计算一组值的加权平均值的几种方法。加权平均值是通过将每个数字与其权重的乘积相加,然后除以所有权重的总和来计算的。可以使用流和reduce()函数来实现这一计算。另外,还介绍了使用自定义收集器来封装计算过程的方法。使用收集器可以更简单地计算加权平均值,并且可以在并行流上使用。

🔎

延伸解读

不同实现方式的权衡

文章展示了计算加权平均值的多种方法,各有优劣。两次流计算和for循环简单直接,但需要可变变量或两次遍历;扩展列表法直观但效率低;reduce()方法能在一次遍历中完成且保持不可变性,但代码较复杂;自定义收集器封装了计算过程,使用简便且支持并行流,但实现复杂。读者应根据具体场景选择:若追求简洁,可选两次流计算;若需并行处理,自定义收集器更合适。

并行流支持与自定义收集器

自定义收集器通过实现supplier、accumulator、combiner和finisher方法,能够无缝支持并行流。在并行流中,每个线程使用独立的RunningTotals实例进行累加,最后通过combiner合并结果。这避免了共享状态带来的线程安全问题,同时提高了计算效率。文章指出,虽然编写收集器较为复杂,但一旦实现,使用起来非常简便,且能自动适应并行处理,适合需要重复使用的场景。

实现细节与注意事项

在实现自定义收集器时,需注意泛型类型T、A、R的定义,以及Characteristics的设置。文章中的收集器标记为UNORDERED,因为加权平均计算不依赖元素顺序。同时,未使用CONCURRENT和IDENTITY_FINISH,以确保正确性。此外,combiner方法必须实现,以支持并行流。这些细节确保了收集器在顺序和并行流中都能正确工作,但开发者需理解其含义,避免误用。

❓

Q&A

Java中如何用Stream计算加权平均值?

可以使用Stream的mapToDouble分别计算分子(每个值乘以其权重之和)和分母(权重之和),然后相除。例如:double top = values.stream().mapToDouble(v -> v.value * v.weight).sum(); double bottom = values.stream().mapToDouble(v -> v.weight).sum(); double result = top / bottom;

用reduce()计算加权平均值时,三个参数分别是什么?

reduce()的三个参数:第一个是初始值(如new WeightedAverage(0,0));第二个是累加器lambda,将当前元素累加到运行总计中;第三个是组合器lambda,用于合并两个并行流的运行总计。最后调用average()得到结果。

自定义收集器计算加权平均值需要实现哪些方法?

需要实现Collector接口的五个方法:supplier()提供可变容器;accumulator()将元素累加到容器;combiner()合并两个容器(用于并行流);finisher()将容器转换为最终结果;characteristics()返回收集器特性。

为什么自定义收集器更适合并行流?

因为收集器通过combiner()方法支持合并多个并行流的中间结果,并且可以指定CONCURRENT等特性来优化并行处理。而reduce()虽然也支持并行,但需要显式传递组合器lambda,使用起来更复杂。

扩展列表法计算加权平均值有什么优缺点?

优点:代码更直观,易于理解,且方便进行其他操作(如求中位数)。缺点:效率较低,因为需要为每个权重创建多个副本,占用更多内存和计算资源。

加权平均值和普通平均值有什么区别?

普通平均值将所有数字相加后除以数字个数;加权平均值则考虑每个数字的权重,计算方法是每个数字乘以其权重后求和,再除以所有权重之和。例如,数字1,3,5,7,9的普通平均值为5,但给定权重10,20,30,50,40后,加权平均值为6.2。

🏷️

标签

➡️

继续阅读