image.png
    ———————————————————————

    1. 组合子(Combinator)
    普通我们常说的一个“函数”:

    函数有两个“自变量”(术语:约束变量),x和y。函数的返回值,也就是应变量,是自变量一系列操作 的结果。比如例子里是返回x和y的和。这样的一个它内部操作依赖的变量全部由参数提供了的”自给自足 “的函数,叫“组合子(Combinator)”。

    Java代码表示就是:
    image.png

    换到编程的概念,强调的就是函数的“作用域”。大多数编程语言都是用一对花括号“{}”标识出作 用域。上面代码里的add()函数被调用之后, int sum=add(2,3); 编译器编译之后,可以理解成是这个样子,函数的参数x和y,是包含在函数add()的作用域里的

    image.png

    或者,函数像下面这样写也可以。这时候x作为函数参数出现,而y作为函数局部变量出现。效果和上面的 例子是一样的。

    image.png


    1. 自由变量
      但有的时候,函数也可以有它自身作用域以外的参数参与。这些在函数作用域以外,由函数的外部环境提
      供的参数就叫“自由变量(Free Variable)”。比如下面这个 x 的函数,返回 x 和 y 的和。这里的y就
      是自由变量。

    image.png
    写成代码就是这样

    image.png

    image.png

    1. 闭包(Closure)
      大白话不怎么严谨的说法就是三点:
      1,一个依赖于自由变量的函数
      2,处在含有这些自由变量的一个外围环境
      3,这个函数能够访问外围环境里的自由变量
      看下面这个Javascript闭包的例子:

    image.png
    对内部函数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主流语法不允许这样的直接的函数套嵌和跨域访问变量。

    1. 类和对象
      但Java中真的不存在闭包吗?正好相反,Java到处都是闭包,所以反而我们感觉不出来在使用闭包。因为
      Java的“对象”其实就是一个闭包。其实无论是闭包也好,对象也好,都是一种数据封装的手段。看下面
      这个类

    image.png

    看上去x在函数add()的作用域外面,但是通过Add类实例化的过程,变量”x“和数值”2“之间已经绑定 了,而且和函数add()也已经打包在一起。add()函数其实是透过this关键字来访问对象的成员字段的。

    1. 内部类是闭包:包含指向外部类的指针
      Java中的内部类就是一个典型的闭包结构。代码如下

    image.png

    下图画的就是上面代码的结构。内部类(Inner Class)通过包含一个指向外部类的引用,做到自由访问外 部环境类的所有字段,变相把环境中的自由变量封装到函数里,形成一个闭包

    image.png


    计算机程序可以粗略的分成,代码+数据。初学者很容易就会将这两者对立起来,会认为代码就是代码,数据就是数据,两者是完全不同的。但实际上,两者可以统一起来的。将代码跟数据统一起来,是学习计算机编程的一道门槛。

    可以参考我以前的回答。回调函数是什么?

    将数据保存起来,以后再使用,会觉得很自然。但将代码保持起来,以后再使用,很多人会觉得很别扭,难以理解。都是因为还没有过那道槛。

    代码指令执行时候,会处于一定的环境,单纯将代码保存下来,还是不够的,需要将代码所处的环境也保存下来。闭包其实是,将代码跟代码所处于的环境做为一个整体来看待。周围的环境,表现为代码所使用的数据。在有些语言中,这个概念叫代码块(block),匿名函数(lambda)等等。

    数据跟代码不再人为割裂开来,统一起来看待。闭包就会是很自然的概念。数据可以传递,从一个地方传递到另一个地方,并且以后再使用。闭包从某个角度来说,也是数据,当然也可以传递,从一个函数传递到另一个函数,也可以保持下来,以后再调用。因为将环境也保持下来了,以后调用的时候,就还原当时的情况,延迟执行,就很容易,很自然地实现了。