6.9 闭包

本章目前为止的例子里,用于函数值或代码块的变量和值都是绑定的。你清楚的知道它们都绑定到哪儿,局部变量或是参数。此外,还可以创建有未绑定变量的代码块。调用函数之前,必须绑定它们;不过,它们可以在局部范围和参数列表之外绑定变量。这就是称它们为闭包的原因。

看一个本章前面看到的totalResultOverRange()方法的变体。这个例子里的loopThrough()方法从1到给定的数进行循环:

FunctionValuesAndClosures/Closure.scala

  1. def loopThrough(number: Int)(closure: Int => Unit) {
  2. for (i <- 1 to number) { closure(i) }
  3. }

loopThrough()方法接收一个代码块作为第二个参数,从1到第一个参数的范围内每个元素都会调用给定的代码块。我们定义一个代码块传给这个方法:

FunctionValuesAndClosures/Closure.scala

  1. var result = 0
  2. val addIt = { value:Int => result += value }

上面的例子里,定义了一个代码块,并将其赋值给变量addIt。在代码块内,变量value绑定到参数。不过,变量result在块或参数列表内是未定义的。实际上,这是绑定到代码块外部的变量result上。代码块伸长了它的手,绑定到一个外部的变量。下面示出在调用loopThrough()方法时如何使用代码块:

FunctionValuesAndClosures/Closure.scala

  1. loopThrough(10) { addIt }
  2. println("Total of values from 1 to 10 is " + result)
  3. result = 0
  4. loopThrough(5) { addIt }
  5. println("Total of values from 1 to 5 is " + result)

将闭包传给方法loopThrough()value绑定到传给loopThrough()的参数上,而result则绑定到loopThrough()调用方上下文里的变量上。

绑定并不是获得变量当前值的一份副本;它实际上是绑定到变量本身。因此,如果将result的值重置为0,闭包也会看到这种变化。而且,如果闭包设置了result的值,那么主代码里也可以看到。下面是另一个例子,闭包绑定到另一个变量product上:

FunctionValuesAndClosures/Closure.scala

  1. var product = 1
  2. loopThrough(5) { product *=_ }
  3. println("Product of values from 1 to 5 is " + product)

在这种情况下,_指向loopThrough()所传入的参数,product绑定到loopThrough()的调用方里叫这个名字的变量上。

三次调用loopThrough()的输出如下:

  1. Total of values from 1 to 10 is 55
  2. Total of values from 1 to 5 is 15
  3. Product of values from 1 to 5 is 120

在本章里,我们探索了Scala里与函数值相关的一些概念,见识到了函数如何成为了第一类公民。你可以看到,在需要增强另一个函数功能的地方,使用这些代码块会带来怎样的裨益。在需要为方法实现的逻辑指定论断(predicate)、查询或约束的地方,都可以使用它们。还可以用它们转换方法的控制流,比如,用在对容器里的值进行迭代的过程中。在本章里,我们已经学到了Scala里一种极具价值的工具。我们会频繁地用到这些工具,在自己的代码里会用到,使用Scala程序库时也会经常用到。在下一章里,我们要去了解Scala里另一个有趣的概念:trait。