6.9 闭包
本章目前为止的例子里,用于函数值或代码块的变量和值都是绑定的。你清楚的知道它们都绑定到哪儿,局部变量或是参数。此外,还可以创建有未绑定变量的代码块。调用函数之前,必须绑定它们;不过,它们可以在局部范围和参数列表之外绑定变量。这就是称它们为闭包的原因。
看一个本章前面看到的totalResultOverRange()
方法的变体。这个例子里的loopThrough()
方法从1到给定的数进行循环:
FunctionValuesAndClosures/Closure.scala
def loopThrough(number: Int)(closure: Int => Unit) {
for (i <- 1 to number) { closure(i) }
}
loopThrough()
方法接收一个代码块作为第二个参数,从1到第一个参数的范围内每个元素都会调用给定的代码块。我们定义一个代码块传给这个方法:
FunctionValuesAndClosures/Closure.scala
var result = 0
val addIt = { value:Int => result += value }
上面的例子里,定义了一个代码块,并将其赋值给变量addIt
。在代码块内,变量value
绑定到参数。不过,变量result
在块或参数列表内是未定义的。实际上,这是绑定到代码块外部的变量result
上。代码块伸长了它的手,绑定到一个外部的变量。下面示出在调用loopThrough()
方法时如何使用代码块:
FunctionValuesAndClosures/Closure.scala
loopThrough(10) { addIt }
println("Total of values from 1 to 10 is " + result)
result = 0
loopThrough(5) { addIt }
println("Total of values from 1 to 5 is " + result)
将闭包传给方法loopThrough()
,value
绑定到传给loopThrough()
的参数上,而result
则绑定到loopThrough()
调用方上下文里的变量上。
绑定并不是获得变量当前值的一份副本;它实际上是绑定到变量本身。因此,如果将result
的值重置为0,闭包也会看到这种变化。而且,如果闭包设置了result
的值,那么主代码里也可以看到。下面是另一个例子,闭包绑定到另一个变量product
上:
FunctionValuesAndClosures/Closure.scala
var product = 1
loopThrough(5) { product *=_ }
println("Product of values from 1 to 5 is " + product)
在这种情况下,_
指向loopThrough()
所传入的参数,product
绑定到loopThrough()
的调用方里叫这个名字的变量上。
三次调用loopThrough()
的输出如下:
Total of values from 1 to 10 is 55
Total of values from 1 to 5 is 15
Product of values from 1 to 5 is 120
在本章里,我们探索了Scala里与函数值相关的一些概念,见识到了函数如何成为了第一类公民。你可以看到,在需要增强另一个函数功能的地方,使用这些代码块会带来怎样的裨益。在需要为方法实现的逻辑指定论断(predicate)、查询或约束的地方,都可以使用它们。还可以用它们转换方法的控制流,比如,用在对容器里的值进行迭代的过程中。在本章里,我们已经学到了Scala里一种极具价值的工具。我们会频繁地用到这些工具,在自己的代码里会用到,使用Scala程序库时也会经常用到。在下一章里,我们要去了解Scala里另一个有趣的概念:trait。