离线下载
PDF版 ePub版

阿拉神农 · 更新于 2018-11-28 11:00:43

闭包

闭包的样子

闭包,英文叫 Closure,是 Groovy 中非常重要的一个数据类型或者说一种概念了。闭包的历史来源,种种好处我就不说了。我们直接看怎么使用它!

闭包,是一种数据类型,它代表了一段可执行的代码。其外形如下:

def aClosure = {//闭包是一段代码,所以需要用花括号括起来..
    String param1, int param2 ->  //这个箭头很关键。箭头前面是参数定义,箭头后面是代码  
    println "this is code" //这是代码,最后一句是返回值,  
   //也可以使用 return,和 Groovy 中普通函数一样  
}

简而言之,Closure 的定义格式是:

def xxx = {paramters -> code}  //或者  
def xxx = {无参数,纯 code}  这种 case 不需要->符号  

说实话,从 C/C++ 语言的角度看,闭包和函数指针很像。闭包定义好后,要调用它的方法就是:

闭包对象.call(参数) 或者更像函数指针调用的方法:

闭包对象(参数)
比如:

aClosure.call("this is string", 100)  或者  
aClosure("this is string", 100)

上面就是一个闭包的定义和使用。在闭包中,还需要注意一点:

如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫 it,和 this 的作用类似。it 代表闭包的参数。

比如:

def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

等同于:

def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

但是,如果在闭包定义时,采用下面这种写法,则表示闭包没有参数!

def noParamClosure = { -> true }

这个时候,我们就不能给 noParamClosure 传参数了!

noParamClosure ("test")  <==报错喔!  

Closure 使用中的注意点

1.省略圆括号

闭包在 Groovy 中大量使用,比如很多类都定义了一些函数,这些函数最后一个参数都是一个闭包。比如:

public static <T> List<T> each(List<T> self, Closure closure)

上面这个函数表示针对 List 的每一个元素都会调用 closure 做一些处理。这里的 closure,就有点回调函数的感觉。但是,在使用这个 each 函数的时候,我们传递一个怎样的 Closure 进去呢?比如:

def iamList = [1,2,3,4,5]  //定义一个 List
iamList.each{  //调用它的 each,这段代码的格式看不懂了吧?each 是个函数,圆括号去哪了?  
      println it
}

上面代码有两个知识点:

  • each 函数调用的圆括号不见了!原来,Groovy 中,当函数的最后一个参数是闭包的话,可以省略圆括号。比如
def  testClosure(int a1,String b1, Closure closure){
      //do something
      closure() //调用闭包  
}

那么调用的时候,就可以免括号!

testClosure (4, "test", {
   println "i am in closure"
} )  //红色的括号可以不写..

注意,这个特点非常关键,因为以后在 Gradle 中经常会出现图 7 这样的代码:

经常碰见图 7 这样的没有圆括号的代码。省略圆括号虽然使得代码简洁,看起来更像脚本语言,但是它这经常会让我 confuse(不知道其他人是否有同感),以 doLast 为例,完整的代码应该按下面这种写法:

doLast({
   println 'Hello world!'
})

有了圆括号,你会知道 doLast 只是把一个 Closure 对象传了进去。很明显,它不代表这段脚本解析到 doLast 的时候就会调用 println 'Hello world!' 。

但是把圆括号去掉后,就感觉好像 println 'Hello world!'立即就会被调用一样!

2.如何确定 Closure 的参数

另外一个比较让人头疼的地方是,Closure 的参数该怎么搞?还是刚才的 each 函数:

public static <T> List<T> each(List<T> self, Closure closure)

如何使用它呢?比如:

def iamList = [1,2,3,4,5]  //定义一个 List 变量  
iamList.each{  //调用它的 each 函数,只要传入一个 Closure 就可以了。  
  println it
}

看起来很轻松,其实:

  • 对于 each 所需要的 Closure,它的参数是什么?有多少个参数?返回值是什么?

我们能写成下面这样吗?

iamList.each{String name,int x ->
  return x
}  //运行的时候肯定报错!  

所以,Closure 虽然很方便,但是它一定会和使用它的上下文有极强的关联。要不,作为类似回调这样的东西,我如何知道调用者传递什么参数给 Closure 呢?

此问题如何破解?只能通过查询 API 文档才能了解上下文语义。比如下图 8:

图 8 中:

  • each 函数说明中,将给指定的 closure 传递 Set 中的每一个 item。所以,closure 的参数只有一个。

  • findAll 中,绝对抓瞎了。一个是没说明往 Closure 里传什么。另外没说明 Closure 的返回值是什么.....。

对 Map 的 findAll 而言,Closure 可以有两个参数。findAll 会将 Key 和 Value 分别传进去。并且,Closure 返回 true,表示该元素是自己想要的。返回 false 表示该元素不是自己要找的。示意代码如图 9 所示:

Closure 的使用有点坑,很大程度上依赖于你对 API 的熟悉程度,所以最初阶段,SDK 查询是少不了的。