闭包

文章目录
  1. 1. 概念
  2. 2. 使用
  3. 3. 其他

概念

关于闭包的概念,我手边scala的书如是说:

闭包是一种特殊的函数值,闭包中封闭或绑定了在另一个作用域或上下文中定义的变量。

在维基百科中的说法是:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

前者说闭包是一个函数,后者说闭包是函数和自由变量一同构成的实体。我个人是比较倾向于后者的。不过细细思考下来,二者也没什么差别,都描述了闭包的几个特征:

  1. 闭包中有一个特殊的函数
  2. 存在一个封闭的作用域,函数就在这个封闭的作用域中;
  3. 在封闭作用域中存在一个函数作用域之外的变量(即自由变量);
  4. 闭包函数绑定了这个自由变量。

来看个关于闭包的程序:

1
2
3
4
5
6
7
8
9
10
11
12
def foo(): Int => Int = {
val i = 1

def bar(num: Int) = {
i + num
}

bar
}

val func = foo()
println(func(2))

在代码中定义了一个方法foo(),方法foo中有一个局部变量i。正常情况下只有在执行foo方法的时候,局部变量i才会存在;foo方法执行完成后,局部变量i就会消失,不再有意义。不过现在在foo方法中定义了一个内部方法bar(),在bar方法中引用了foo方法的局部变量i,最后是将bar转为函数值作为foo方法的返回值。

这段代码中的bar函数绑定了它的作用域之外的变量i。按照前文的说明:bar函数和变量i共同构成了一个闭包。

现在想一下:调用foo方法返回的bar函数时,foo的局部变量i是否有效? 显然的,根据维基百科的解释,虽然foo方法已经执行结束,但是局部变量i仍然和函数bar一同存在,调用bar函数,i仍然有效。

执行这段代码看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scala> def foo(): Int => Int = {
| val i = 1
|
| def bar(num: Int) = {
| i + num
| }
|
| bar
| }
foo: ()Int => Int

scala> val func = foo()
func: Int => Int = <function1>

scala> println(func(2))
3

代码的执行结果和前面的推论是一致的。

根据这段代码我们可以看到闭包的“闭”封闭的是函数的外部作用域。在这个例子中,封闭的就是bar函数外的foo方法的作用域。整个闭包只有一个对外的通道,即bar函数,我们可以通过bar函数来访问封闭的作用域的内容。

再来说明下闭包定义中的绑定两个字。闭包中的绑定并不是获得闭包绑定自由变量的一份副本,而是直接绑定到变量本身。在闭包中对自由变量的调整会影响到闭包外部的自由变量;同样的,外部自由变量的变化,闭包也会受到影响。下面这个例子应该可以说明这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def foo(): () => Int = {
var i = 0

def bar(): Int = {
i += 1
i
}

bar
}

val func = foo()

println(func())
println(func())
println(func())

在代码中定义了一个闭包。闭包中的自由变量是i,在函数bar中对自由变量i做了调整。在闭包外,三次调用了闭包函数。这段代码执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1
2
3
```

对比输出结果我们可以看到:每次调用闭包函数bar都会对自由变量i产生影响;而自由变量i的变化,又会在下一次执行bar函数的时候发挥作用。


## 思考 - 对象和闭包

看完了闭包的示例代码后,回过头来再思考一下闭包的概念:

> 闭包是一种特殊的函数值,闭包中封闭或绑定了在另一个作用域或上下文中定义的变量。


那根据这个概念,下面这段代码是不是闭包:
```scala
class A {

private val i = 1

def bar(num: Int): Int = {
i + num
}
}

算了,还是直接看这段代码吧:

1
2
3
4
5
6
7
8
9
10
class A {

private val i = 1

def bar(num: Int): Int = {
this.i + num
}
}

new A().bar(2)

这里定义了一个类A,类A有一个私有变量i,这个变量i只能通过bar方法访问,换言之bar方法也是引用了一个它的作用域之外的变量i。那么class A这个结构应该也算是闭包喽!?

但是这里的bar方法需要通过类A的对象来调用,它本质上是类A的一个实例成员,而非是一个独立的函数;另外这里的bar方法引用的变量i是绑定在类A的对象上,而非绑定在bar方法上,所以从根本上这种形式就不满足闭包的定义,所以不是闭包!?

对于这样定义的类是否是闭包,以上是两种观点,也是我纠结之处。也查了些资料,然而各种说法莫衷一是。思考了一段时间后决定停止这种纠结,仅从一点出发:只在函数作为头等公民的语境中来讨论闭包

对象和函数各有自己不同的使用场景。在scala这种既支持函数又面向对象的语言中,使用对象可以减少一些复杂度,使用函数可以提升一些灵活性。但是把函数的概念放到面向对象的语境中,或者反过来都是没有意义的事情。

使用

目前想到的关于的闭包的用法主要有两种:一种是将闭包作为返回值,就如前面的例子;另一种是将闭包作为参数,如下面的例子:

1
2
3
4
5
6
7
8
9
10
val max = 10

def foo(f: (Int) => Boolean): Unit = {
val arr = Array(1, 1, 2, 3, 5, 8, 13, 21, 34, 55)
arr.filter(f).foreach(println)
}

val bar = (num: Int) => num > max

foo(bar)

又想到了一个应用场景,比如在一些对于面向对象支持不是很好的语言,可以用闭包来构建对象(用scala来举这个例子,有些费力,懒得写了)。严格来说,这种用法也是将函数用作闭包的返回值。

另外scala的curry化也是闭包的一种应用。

其他

在知乎上有各路大神对闭包进行了讨论,有兴趣可以看一下:什么是闭包