1. 简介

在本教程中,我们将深入探讨 Scala 中 foldLeftreduceLeft 的区别。

这两个方法都是用于从左到右遍历集合的高阶函数。它们都会对每个元素应用一个函数,并通过一个累加器将结果传递给下一次执行。从行为上看,它们与递归函数非常相似。

2. 方法签名

为了更好地理解它们的行为,我们先来看一下 foldLeftreduceLeft 的方法签名。

2.1. foldLeft 签名

foldLeft 遍历一个集合,对每个元素应用函数,并可以返回任意类型的结果,该类型由初始值(seed value)决定。

其方法签名为:

def foldLeft[B](z: B)(op: (B, A) ⇒ B): B

从签名中可以看出,foldLeft 需要传入一个初始值 z,该值类型与返回值类型一致,作为累加器的起点。A 是集合中元素的类型,与返回值类型 B 之间没有强制关联。

优点:类型灵活,适合做类型转换或聚合操作

2.2. reduceLeft 签名

reduceLeft 用于将集合中的元素通过函数合并为一个单一结果。

其方法签名为:

def reduceLeft[B >: A](op: (B, A) ⇒ B): B

乍一看与 foldLeft 很像,但有两点关键区别:

  • 没有初始值:累加器的初始值就是集合的第一个元素
  • ⚠️ 类型约束更强B >: A 表示 B 必须是 A 的父类型(上界),也就是说返回值类型不能随意改变

适用场景:聚合操作,比如求和、找最大值等

3. 实战示例

下面通过两个具体例子来展示 foldLeftreduceLeft 的使用差异。

3.1. foldLeft 示例

foldLeft 的一大优势在于其类型灵活性,可以将集合转换为完全不同的类型。

假设我们有一个用户系统,每个用户由 ID 和 Person 对象组成:

case class Person(name: String, age: Int)

用户数据存储为 Map[Int, Person]

val users: Map[Int, Person] = 
  Map(1 -> Person("Tom", 10), 2 -> Person("Gillian", 13), 3 -> Person("Sarah", 17), 4 -> Person("David", 20))

现在我们想把用户数据从 Map[Int, Person] 转换为 List[Person],可以使用 foldLeft

val peopleList: List[Person] = users.foldLeft(List.empty[Person])((people, current) => { 
  people :+ current._2 
})
assert(peopleList == List(Person("Tom", 10), Person("Gillian", 13), Person("Sarah", 17), Person("David", 20)))

用途广泛,特别适合类型转换

3.2. reduceLeft 示例

有了用户列表后,我们想找出年龄最小的用户。这时就可以用 reduceLeft

val youngestPerson: Person = peopleList.reduceLeft((youngestPerson, currentPerson) => { 
  if (youngestPerson.age > currentPerson.age) {
    currentPerson 
  } else {
    youngestPerson
  }
})
assert(youngestPerson == Person("Tom", 10))

这里我们将 List[Person] 缩减为单个 Person 对象,即年龄最小的那个。

适合“归约”操作,但注意不能用于空集合

⚠️ 注意reduceLeft 不能用于空集合,否则会抛异常;而 foldLeft 因为有初始值,可以安全处理空集合。

4. 总结

特性 foldLeft reduceLeft
是否需要初始值 ✅ 是 ❌ 否
类型是否可变 ✅ 可以返回任意类型 ⚠️ 返回类型必须是元素类型的父类
空集合是否安全 ✅ 安全 ❌ 不安全
适用场景 类型转换、聚合、构建新结构 聚合操作、缩减为单一值

总的来说,如果你需要更强的类型控制和更灵活的操作,优先使用 foldLeft;如果你只是想对集合进行聚合操作,并且类型一致,reduceLeft 更简洁。

📚 本文代码示例已上传至 GitHub


原始标题:The Difference Between foldLeft and reduceLeft in Scala

« 上一篇: Scalaz背后的原则
» 下一篇: 同步处理Futures