由于需要看Spark源码,发现其中用到了许多闭包的地方,所以很有必要搞明白闭包这玩意儿的用法。
网上对scala闭包最多的解释就是通过闭包,可以在超过局部变量作用域的地方仍然能够使用局部变量的值,这话反正我一开始读起来是似懂非懂的,看起来好像是类似于扩充了局部变量的使用范围,但是这又是如何实现的呢?带着疑问,看了一些资料后,发现还是直接用代码好解释一些。
直接上测试代码:
object ClosureTest{
def addTwo(one:Int)=(two:Int)=>(one+two)
def main(args: Array[String]): Unit = {
val func1 = addTwo(1)
println(func1(2))
}
}
对于以上的ClosureTest.scala
执行scalac进行编译得到class文件,我们来查看一下对应编译结果目录下居然生成了三个class文件,其中两个是ClosureTest.scala
scala类自身生成的两个可以理解,但是为什么会多出一个呢,这个类的代码极其简单,很显然多出的一个类只能是由于以上的闭包addTwo
方法产生的,这也就验证了闭包原理详解中说的闭包其实是通过给闭包方法生成一个类,然后每次调用闭包时,其实是相当于先调用了一个闭包对应的类的构造方法得到一个类,而这个类的构造函数入参就是我们所谓的局部变量,即通过和类进行绑定实现了所谓的在局部变量作用域以外的地方也能够访问到该局部变量,我们再通过javap来简单看一下为这个闭包生成的类的代码:
$ javap 'ClosureTest$$anonfun$addTwo$1.class'
Compiled from "ClosureTest.scala"
public final class test.scala.ClosureTest$$anonfun$addTwo$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
public static final long serialVersionUID;
public final int apply(int);
public int apply$mcII$sp(int);
public final java.lang.Object apply(java.lang.Object);
public test.scala.ClosureTest$$anonfun$addTwo$1(int);
}
这里也进一步验证了参考博客中说的,对于闭包的使用,是先通过局部变量作为入参调用构造函数后,得到一个类,通过和类绑定使得在其它地方也能实现访问这个局部变量,然后如果再后续传入第二个参数得到闭包结果时,其实就是调用这个类的apply方法。
看到这里我也理解了之前看到的一个关于闭包的解释,闭包和普通函数的区别在于闭包是有状态的,而函数是没有状态的,这里所谓的状态就是因为每个闭包在构造的时候,传入的第一个参数就是所谓的状态,通过这个入参构造了一个有状态的类。
参考:https://www.jianshu.com/p/8f24150fad2a