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()

reducefold 类似,但 不接受初始值,而是使用第一个元素作为初始累加值:

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


原始标题:Find Average of All Items in a List in Kotlin