8. 闭包
8.1 闭包的语法结果如下,如果没有参数,那么参数列表和 ->可以缺省.比如下面的clos引用的闭包,没有参数只有一个执行语句,它可以被clos调用call方法来执行.
----------------------------------------------------
{逗号分隔的参数列表 -> 执行语句块}
def clos ={println "Hello World"}
clos.call()
----------------------------------------------------
我们也可以加入参数,比如下面的语句:
----------------------------------------------------
def clos = {param -> println "Hello ${param}"}
clos.call('pwy') // 'Hello pwy'
clos.call('tracy') // 'Hello tracy'
clos('pwy') // 'Hello pwy', call可以是缺省的
----------------------------------------------------
闭包中有一个默认的隐含参数it可以被调用,如:
----------------------------------------------------
def clos = {println "Hello ${it}"}
clos.call('pwy') // 'Hello pwy'
clos.call('tracy') // 'tracy'
clos('pwy') // 'Hello pwy' call省略
----------------------------------------------------
闭包可以引用它定义之前的已经定义的变量,下面的变量name是在闭包clos定义之前定义的,所以clos被调用的时候可以引用name的值,它的值初始化为'pwy'.
----------------------------------------------------
def name ='pwy'
def clos = {param -> println "${name} ${param}"}
clos.call('handsome') // 'pwy handsome'
name = '2Bing'
clos.call('handsome') // '2Bing handsome'
def test(col){
def name = 'others' // 此处局部变量name 是在闭包clos定义之后定义的,所以clos不能引用它的值
col.call('handsome')
}
test(clos) // '2Bing handsome'
----------------------------------------------------
我们可以调用一个带有clos参数的方法,groovy提供了简单的方式使得代码更容易阅读,参数clos从参数列表中移除,放到方法括号后面,所以下面两种形式都是对的.
----------------------------------------------------
test(clos) // 参数闭包clos在方法中
test() clos // 参数闭包clos在方法外
----------------------------------------------------
闭包通常应用于集合,我们循环集合中的所有元素,然后对每个元素调用闭包.比如,所有的数值类型对象都支持upto方法 void upto(Number to,Closure closure)
我们可以这样使用它, 1.toto(10) {p->...}, 然后每次迭代默认参数值分别为1,2,3....10.
----------------------------------------------------
def factorial = 1
1.upto(5) {num -> factorial*=num} // 从1开始迭代到5,每次变量factorial分别乘以闭包默认参数1,2,3,4,5将值返回赋给本身
println "Factorial(5):${factorial} " // 打印输出 "Factorial(5):120"
----------------------------------------------------
8.2 闭包、集合、字符串
一些List,Map,String对象的方法可以接受闭包作为参数,这种闭包和集合的混合方式给groovy提供了解决通用程序问题的比较优雅的解决方案.比如each方法的使用:
void each(Closure closure) 用来循环List,Map,String,并对每个元素使用闭包.
----------------------------------------------------
[1,2,3,4].each{ print it} // 1234
['pwy':'handsome','2Bing':'beautiful'].each{ print it} // pwy=handsome2Bing=beautiful
['pwy':'handsome','2Bing':'beautiful'].each{ println "${it.key} maps to: ${it.value}" } // pwy maps to: handsome 2Bing maps to: beautiful
'pwy'.each{print it} // pwy
----------------------------------------------------
find方法从集合中查找匹配某些条件的第一个值,在闭包中指定的被集合元素匹配的条件必须是某个Boolean表达式,find方法返回第一个找到的值,如果没找到则返回null
----------------------------------------------------
def value = [1,3,5,7,9].find{element -> element>6}
println "Found: ${value}" // "Found:7"
def value2 = [1,3,5,7,9].find{element -> element >10}
println "Found: ${value2}" // "Found:null"
def value3 = ['pwy':27,'2Bing':28].find{element -> element.value>27}
println "Found: ${value3}" // "Found:2Bing=28" 注意当我们对map对象使用find方法时,参数和返回的值都是一个Map.Entry对象.
----------------------------------------------------
findAll方法可以从集合中查找出匹配到指定条件的所有元素,并将结果作为list返回 List findAll(Closure closure)
----------------------------------------------------
def value = [1,3,5,7,9].find{element -> element>6}
value.each{print it} // "79"
----------------------------------------------------
any方法通过循环迭代集合中的所有元素,检查是否至少有一个元素符合闭包中的boolean表达式,如果检查是这样返回true,否则返回false.
every方法通过循环迭代集合的所有元素,检查是否每个元素都符合闭包中的boolean表达式,如果检查是这样返回true,否则返回false.
boolean any(Closure closure)
boolean every(Closure closure)
-----------------------------------------------------
def value = [1,3,5,7,9].any{element -> element>8}
println "Found: ${value}" // "Found:true"
def value2 = [1,3,5,7,9].every{element -> element>3}
println "Found: ${value2}" // "Found:false"
-----------------------------------------------------
collect方法循环迭代集合中的所有元素,通过闭包改变每个元素的值放入一个新的List中返回. List collect(Closure closure)
-----------------------------------------------------
def list = [1,3,5].collect{element -> return element*element} // return 可以省略
println "list: ${list}" // [1,9,25]
def list2 = (0..5).collect{element -> element+1}
println "list2: ${list2}" // [1,2,3,4,5,6]
------------------------------------------------------
以上例子中,方法collect可以用于范围对象,这是允许的因为Range接口继承与List接口,所以可以作为list来使用.
最后一个是inject方法,它有一个初始值参数,inject方法循环迭代一个List,并使用闭包处理第一个元素,将返回结果赋值给下个循环迭代的初始值参数,直到循环结束,
返回最后的计算结果
Object inject(Object obj,Closure closure)
------------------------------------------------------
def number = [1,2,3,4].inject(1){previous,element -> previous*element}
println "number: ${numbers}"
------------------------------------------------------
8.3 其他的闭包的特征
由于闭包是一个对象,它可以作为一个方法的参数.下面的filter方法带有两个参数,一个list对象和一个闭包对象.该方法可以查询list中所有符合闭包指定条件的元素.
------------------------------------------------------
def filter (list,predicate){
return list.findAll(predicate)
}
def idEven ={x -> return (x%2==0)}
def isOdd ={x -> return ! isEven(x)}
def table = [1,2,3,4,5,6]
def evens = filter(table,isEven)
println "evens: ${evens}" // [2,4,6]
def odds = fileter(table,isOdd)
println "odds: ${odds}" // [1,3,5]
------------------------------------------------------
闭包还可以作为其他闭包的参数,下面的例子中先定义了一个闭包tabkeWhile,它有两个参数:闭包predicate,和list,这个方法循环迭代list中的每个元素,将符合闭包条件 的元素放入一个集合result中,直到找到不符合条件的元素,结束循环,返回reuslt集合.
------------------------------------------------------
def takeWhile ={predicate,list ->
def result =[]
for (element in list){
if(predicate(element)){
result << element
} else {
return result
}
}
}
def table1 =[11,12,13,14,15,16]
def table2 =[11,13,15,16,18]
def idEven ={x -> return (x%2==0)}
def isOdd ={x -> return ! isEven(x)}
def evens = takeWhile.call(isEven,table1)
println "evens: ${evens}" // [12]
def odds = takeWhile.call(isOdd,table2)
println "odds: ${odds}" // [11,13,15]
-------------------------------------------------------