———————————————————————
1. 组合子(Combinator)
普通我们常说的一个“函数”:
函数有两个“自变量”(术语:约束变量),x和y。函数的返回值,也就是应变量,是自变量一系列操作 的结果。比如例子里是返回x和y的和。这样的一个它内部操作依赖的变量全部由参数提供了的”自给自足 “的函数,叫“组合子(Combinator)”。
Java代码表示就是:
换到编程的概念,强调的就是函数的“作用域”。大多数编程语言都是用一对花括号“{}”标识出作 用域。上面代码里的add()函数被调用之后, int sum=add(2,3); 编译器编译之后,可以理解成是这个样子,函数的参数x和y,是包含在函数add()的作用域里的
或者,函数像下面这样写也可以。这时候x作为函数参数出现,而y作为函数局部变量出现。效果和上面的 例子是一样的。
- 自由变量
但有的时候,函数也可以有它自身作用域以外的参数参与。这些在函数作用域以外,由函数的外部环境提
供的参数就叫“自由变量(Free Variable)”。比如下面这个 x 的函数,返回 x 和 y 的和。这里的y就
是自由变量。
写成代码就是这样
- 闭包(Closure)
大白话不怎么严谨的说法就是三点:
1,一个依赖于自由变量的函数
2,处在含有这些自由变量的一个外围环境
3,这个函数能够访问外围环境里的自由变量
看下面这个Javascript闭包的例子:
对内部函数function(x)来讲,y就是自由变量,而且function(x)的返回值,依赖于这个外部自由变量y。而
往上推一层,外围Add(y)函数正好就是那个包含自由变量y的环境。而且Javascript的语法允许内部函数
function(x)访问外部函数Add(y)的局部变量。满足这三个条件,所以这个时候,外部函数Add(y)对内部函
数function(x)构成了闭包。
闭包的结构,如果用λ演算表达式来写,就是多参数的Currying技术。
λx.λy.x+y
但在Java中我们看不到这样的结构。因为Java主流语法不允许这样的直接的函数套嵌和跨域访问变量。
- 类和对象
但Java中真的不存在闭包吗?正好相反,Java到处都是闭包,所以反而我们感觉不出来在使用闭包。因为
Java的“对象”其实就是一个闭包。其实无论是闭包也好,对象也好,都是一种数据封装的手段。看下面
这个类
看上去x在函数add()的作用域外面,但是通过Add类实例化的过程,变量”x“和数值”2“之间已经绑定 了,而且和函数add()也已经打包在一起。add()函数其实是透过this关键字来访问对象的成员字段的。
- 内部类是闭包:包含指向外部类的指针
Java中的内部类就是一个典型的闭包结构。代码如下
下图画的就是上面代码的结构。内部类(Inner Class)通过包含一个指向外部类的引用,做到自由访问外 部环境类的所有字段,变相把环境中的自由变量封装到函数里,形成一个闭包
计算机程序可以粗略的分成,代码+数据。初学者很容易就会将这两者对立起来,会认为代码就是代码,数据就是数据,两者是完全不同的。但实际上,两者可以统一起来的。将代码跟数据统一起来,是学习计算机编程的一道门槛。
可以参考我以前的回答。回调函数是什么?
将数据保存起来,以后再使用,会觉得很自然。但将代码保持起来,以后再使用,很多人会觉得很别扭,难以理解。都是因为还没有过那道槛。
代码指令执行时候,会处于一定的环境,单纯将代码保存下来,还是不够的,需要将代码所处的环境也保存下来。闭包其实是,将代码跟代码所处于的环境做为一个整体来看待。周围的环境,表现为代码所使用的数据。在有些语言中,这个概念叫代码块(block),匿名函数(lambda)等等。
数据跟代码不再人为割裂开来,统一起来看待。闭包就会是很自然的概念。数据可以传递,从一个地方传递到另一个地方,并且以后再使用。闭包从某个角度来说,也是数据,当然也可以传递,从一个函数传递到另一个函数,也可以保持下来,以后再调用。因为将环境也保持下来了,以后调用的时候,就还原当时的情况,延迟执行,就很容易,很自然地实现了。