1. 概述
在数据分析和报表生成中,计算数值列表的平均值是一个非常常见的需求。平均值能反映数据集的中心趋势,是统计分析中的基础操作。
本文将系统介绍在 Kotlin 中计算列表所有元素平均值的多种方式,涵盖从传统循环到函数式编程的各种实现方案。这些方法各有适用场景,掌握它们有助于写出更高效、更具可读性的代码。
我们使用的示例数据如下:
val numbers = listOf(10, 2, 3, 40, 5) // 平均值为 (10+2+3+40+5)/5 = 12.0
后续所有方法都会基于这个 numbers
列表进行验证。
2. 使用循环结构
虽然 Kotlin 更推荐函数式风格,但在某些场景下使用循环依然直观且易于理解。
2.1. for 循环
通过传统的 for
循环遍历列表并累加求和,最后除以元素个数得到平均值:
fun averageUsingForLoop(numbers: List<Int>): Double {
var sum = 0.0
for (num in numbers) {
sum += num
}
val average = sum / numbers.size
return average
}
测试验证:
val average: Double = averageUsingForLoop(numbers)
assertEquals(12.0, average)
✅ 简单直接,适合初学者理解逻辑流程。
⚠️ 注意:sum
必须声明为 Double
类型,否则整数相加可能导致精度丢失。
2.2. forEach 循环
利用集合的 forEach
扩展函数替代显式 for
循环,代码更简洁:
fun averageUsingForEachLoop(numbers: List<Int>): Double {
var sum = 0.0
numbers.forEach { sum += it }
val average = sum / numbers.size
return average
}
测试验证:
val average: Double = averageUsingForEachLoop(numbers)
assertEquals(12.0, average)
✅ 函数式风格初体验,避免了索引管理。
❌ 缺点依然是可变变量(var sum
),不够“纯”。
3. 使用扩展函数
Kotlin 标准库提供了丰富的集合操作扩展函数,是更推荐的写法。
3.1. 使用 average()
最简单的方式——直接调用 List
上的 average()
扩展函数:
val average: Double = numbers.average()
测试验证:
assertEquals(12.0, average)
✅ 一行代码解决问题,语义清晰,性能良好。
⚠️ 注意:该函数仅适用于数值类型列表(如 Int
, Double
等)。
这是 首选方案,除非有特殊需求,否则应优先使用。
3.2. 使用 sumByDouble()
sumByDouble
允许我们对每个元素执行转换后求和,非常适合需要类型转换的场景:
fun averageUsingSumByDouble(numbers: List<Int>): Double {
val sum = numbers.sumByDouble { it.toDouble() }
val average = sum / numbers.size
return average
}
测试验证:
val average: Double = averageUsingSumByDouble(numbers)
assertEquals(12.0, average)
✅ 支持自定义映射逻辑,灵活性高。
✅ 避免了手动循环,不可变性更好。
💡 小贴士:it
表示当前遍历的元素,这是 Kotlin Lambda 中的默认参数名。
3.3. 使用 fold()
fold
是函数式编程中的经典操作,用于累积计算:
fun averageUsingFold(numbers: List<Int>): Double {
val sum = numbers.fold(0.0) { acc, num -> acc + num }
val average = sum / numbers.size
return average
}
acc
是累加器,初始值为0.0
num
是当前元素- 每次迭代将
num
加到acc
上
测试验证:
val average: Double = averageUsingFold(numbers)
assertEquals(12.0, average)
✅ 支持任意初始值,安全性高(空列表返回初始值)。
✅ 不依赖列表内容作为起点,行为更 predictable。
3.4. 使用 reduce()
reduce
与 fold
类似,但 不接受初始值,而是使用第一个元素作为初始累加值:
fun averageUsingReduce(numbers: List<Int>): Double {
val sum = numbers.reduce { acc, num -> acc + num }
val average = sum.toDouble() / numbers.size
return average
}
测试验证:
val average: Double = averageUsingReduce(numbers)
assertEquals(12.0, average)
⚠️ 踩坑警告:如果列表为空,reduce
会抛出 NoSuchElementException
!
✅ 性能略优于 fold
(少一个参数绑定)。
❌ 不适用于可能为空的列表,使用时务必先判空。
4. 使用 Sequence 处理大数据
当处理大型数据集或复杂链式操作时,建议使用 Sequence
来提升性能。
Sequence
采用惰性求值机制,避免创建中间集合,节省内存和 CPU 开销。
fun averageUsingSequence(numbers: List<Int>): Double {
val sum = numbers.asSequence().map { it.toDouble() }.sum()
val average = sum / numbers.size
return average
}
测试验证:
val average: Double = averageUsingSequence(numbers)
assertEquals(12.0, average)
✅ 优势明显:
- 惰性执行,只在终端操作(如
sum()
)时触发计算 - 链式操作中不会生成临时 List
- 对大集合更友好
❌ 小数据集上可能因额外开销反而更慢
📌 建议:仅在以下情况使用 Sequence
:
- 数据量大(> 10k 元素)
- 存在多个中间操作(如 filter → map → distinct)
5. 总结
方法 | 优点 | 缺点 | 推荐场景 |
---|---|---|---|
for / forEach |
易懂,控制力强 | 可变变量,冗长 | 教学或简单脚本 |
average() |
最简洁,安全 | 仅限数值类型 | ✅ 日常首选 |
sumByDouble() |
灵活转换 | 稍 verbose | 需要映射时 |
fold() |
安全,支持初始值 | 略啰嗦 | 复杂累积逻辑 |
reduce() |
高性能 | 空列表崩溃 | 已知非空列表 |
Sequence |
惰性求值,省内存 | 小数据有开销 | 大数据处理 |
📌 最终建议:
- 绝大多数情况下直接使用
numbers.average()
- 若需自定义映射逻辑,用
sumByDouble
- 大数据处理考虑
asSequence()
- 避免手动循环,除非有特殊控制需求
示例代码已上传至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/kotlin-math-2