Skip to content

Scala 闭包

闭包(Closure)是指引用了自身函数体外部定义变量(称为 自由变量)的函数。函数“关闭”(closes over)这些变量,意味着即使函数在与其定义时不同的作用域中执行,它也能保持对这些变量的访问。

考虑一个简单的函数字面量:

val addTen = (x: Int) => x + 10

这里,函数体 x + 10 中使用的唯一变量是 x,它是函数的参数。这个函数不是一个闭包,因为它没有自由变量。

现在,让我们引入一个自由变量:

var factor = 5 // 在函数外部定义的变量
// 这个函数字面量使用了 'factor',它是一个自由变量
val multiplier = (i: Int) => i * factor

multiplier 函数是一个闭包,因为它依赖于在外部作用域中定义的 factor。闭包捕获的是 factor 的 引用,而不仅仅是其定义时的值。

object ClosureDemo {
var factor = 3 // 自由变量
val multiplier = (i: Int) => i * factor // 捕获 'factor' 的闭包
def main(args: Array[String]): Unit = { // 或者使用 @main 注解
println(s"multiplier(1) value = ${multiplier(1)}") // 使用 factor = 3
println(s"multiplier(2) value = ${multiplier(2)}") // 使用 factor = 3
// 改变自由变量
factor = 10
println("--- Factor changed to 10 ---")
println(s"multiplier(1) value = ${multiplier(1)}") // 使用 factor = 10
println(s"multiplier(2) value = ${multiplier(2)}") // 使用 factor = 10
}
}

输出:

multiplier(1) value = 3
multiplier(2) value = 6
--- Factor changed to 10 ---
multiplier(1) value = 10
multiplier(2) value = 20

正如输出所示,闭包 multiplier 每次调用时都会读取被捕获变量 factor 的 当前 值。如果被捕获的变量是可变的(var),那么对其的改变会反映在闭包后续的调用中。

闭包也可以捕获不可变变量(val)。在这种情况下,捕获的值永远不会改变。

val fixedIncrement = 5
val addFixed = (x: Int) => x + fixedIncrement // 捕获不可变的 'fixedIncrement'
@main def ImmutableClosureDemo(): Unit = {
println(addFixed(10)) // 输出: 15
// fixedIncrement = 10 // 这会引发编译错误,因为 'fixedIncrement' 是一个 val
}

闭包是函数式编程的基础,在 Scala 中被广泛使用:

  • 事件处理器/回调函数(Event Handlers/Callbacks): 处理函数通常需要访问定义其作用域中的变量(例如,UI 元素、状态变量)。
  • 异步编程(Asynchronous Programming): 传递给异步操作(如 Future 或 IO)的回调函数通常会捕获操作完成时所需的上下文。
  • 构建领域特定语言(DSLs): 闭包可以创建具有特定上下文行为的函数。
  • 偏函数应用(Partial Application)与柯里化(Currying): 这些技术通常会产生闭包。

理解闭包对于掌握 Scala 中的函数式编程以及利用像 Akka、Cats 或 ZIO 这样的库至关重要。