假设我们有一个引用 x 指向一个闭包,我们能通过 x.call()来调用闭包,或者简单的 x(),你大概猜测到可以传递任何参数给闭包方法。
我们通过一个简单的例子开始,列表 5.5 显示了同一个闭包的两种不同的调用方式。
我们首先声明了一个十分简单的闭包——这个闭包返回两个参数的和,然后我们直接调用闭包和使用 call 方法调用闭包,这两种方式调用闭包获得的效果是一样的。
现在,我们来试试更复杂的一些东西,在列表 5.6 中,我们从一个方法内部调用一个闭包,例子显示了闭包执行的时机:
还记得我们在列表 3.7 中进行正则表达式的性能测试吗?我们需要重复基准逻辑,我们没有声明怎样的基准 something。现在你知道了,你可以传递一个闭包到 b enchmark 方法,在这个方法里,可以做一些预处理和后处理工作。
在(1)的地方我们把闭包作为最后一个参数,这样在调用该方法的时候可以使用闭包的简单语法声明方式。在这个例子中,我们声明了闭包的类型,这样仅仅是为了让事情显得更明了,Closure 类型是可选的。
实际上,开始基准测试的地方是在(2),从一个常规的方式来说,这里是像打开文件或 者连接数据库之类的预处理代码,这通常发生在资源处理的时候。
在(3),我们根据 repeat 参数重复的调用闭包。我们把当前正在重复的次数传递给闭包,这样使得工作更加有趣,一般情况下是把一个资源传递给闭包。
在(4)的地方停止测试并且计算调用闭包所用的时间,这里放置的是后处理工作:关闭文件、冲洗缓冲区(flushing buffers)、将连接返回给连接池等等。
在(5)开始测试,我们能传递逻辑给 benchmark 方法,注意我们使用闭包的简单声明方式并且使用魔幻的 it 来引用当前的计数,我们可以得知一般的数字除法比优化的 intdiv 方法要多运行 15 倍。
顺便说一下:这种类型的基准测试不太严谨,所有的动作都能严重的影响测试的效果:
计算机的配置、操作系统、当前计算机的加载过程、JDK 的版本、JIT 编译器和 Hot spot 设置等等。
图 5.2 显示了一般情况下通过声明方式创建闭包然后调用闭包的 UML 序列图,在 caller 上进行方法调用,并且 caller 回调到给定的闭包。
![]()
图 5.2 闭包的典型的调用方式
在调用闭包的时候,需要传递所有准确的参数给闭包,除非闭包定义了缺省值,当忽略了某个参数的时候,这个参数的缺省值被使用,下面的代码是列表 5.5 的一个变种,第二个参数有一个缺省值,这样就可以进行两种调用——一是传递两个参数,另外一种是传递一个参数(第二个参数使用缺省值):def adder = { x, y=5 -> return x+y }
assert adder(4, 3) == 7 assert adder.call(7) == 12对于在闭包中使用缺省值参数的方式,同样的规则可以应用到方法上面;同样,闭包可以使用的可变长度的参数列表也适用在方法上。
在这里,你应该知道传递闭包给方法和怎样回调执行有了一个基本的理解,也可以看看 图 5.2 的 UML 图,每当你传递一个闭包给方法的时候,你应该确保有一种方式进行回调(也 许仅仅是条件),这依赖方法的逻辑实现,在下一节,将看到闭包更多的功能。

