1. 语法
1.1. 定义一个闭包
闭包是Groovy中非常重要的一个数据类型或者说一种概念。闭包是一种数据类型,它代表了一段可执行的代码
闭包的语法定义: { [closureParameters -> ] statements }
其中[closureParameters->]部分是一个可选的以逗号分隔的参数列表
statements可以为空,一行或者多行代码。
当参数列表确定时,->
是必须的,他负责把参数和闭包的代码块分开,代码块可以由一行或者多行语句组成
一些闭包常用的定义形式:
{ item++ }
{ -> item++ }
{ println it }
{ it -> println it }
{ name -> println name }
{ String x, int y ->
println "hey ${x} the value is ${y}"
}
{ reader ->
def line = reader.readLine()
line.trim()
}
如果闭包没有定义参数的话,则隐含有一个参数,这个参数的名字交it,和this作用类似,it代表闭包的参数。
1.2. 闭包作为一个对象
闭包实质上是一个groovy.lang.Closure
类的实例,虽然他是一个代码块,但是他可以为任何一个变量或者字段赋值,闭包中可以包含代码的逻辑,闭包中的最后一行语句,表示该闭包的返回值,不论该语句是否有return
关键字
//可以将闭包分配给一个变量,这个变量就是groovy.lang.Closure的一个实例
def listener = { e -> println "Clicked on $e.source" }
assert listener instanceof Closure
//如果不使用def,可以将一个闭包分配给一个类型为groovy.lang.Closure的变量
Closure callback = { println 'Done!' }
//可以通过使用groovy.lang.Closure的泛型来指定闭包的返回类型
Closure isTextFile = {
File it -> it.name.endsWith('.txt')
}
1.3. 调用闭包
闭包作为一个匿名的代码块,可以像方法那样被调用,如果定义了一个不需要参数的闭包:
def code = { 123 }
闭包内的代码只有在闭包被调用时才会执行,调用可以像常规方法那样来完成
assert code() == 123
或者,可以显式的调用call
方法
assert code.call() = 123
example:
def isOdd = { int i -> i%2 != 0 }
assert isOdd(3) == true
assert isOdd.call(2) == false
def isEven = { it%2 == 0 }
assert isEven(3) == false
assert isEven.call(2) == true
和方法不一样的是,闭包始终有一个返回值
2. 参数
2.1. 正常参数
闭包的参数遵循与常规方法的参数相同的原则
- 可选的类型
- 名字
- 可选的默认值
参数用,
分隔
def closureWithOneArg = { str -> str.toUpperCase() }
assert closureWithOneArg('groovy') == 'GROOVY'
def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY'
def closureWithTwoArgs = { a,b -> a+b }
assert closureWithTwoArgs(1,2) == 3
def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }
assert closureWithTwoArgsAndExplicitTypes(1,2) == 3
def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }
assert closureWithTwoArgsAndOptionalTypes(1,2) == 3
def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }
assert closureWithTwoArgAndDefaultValue(1) == 3
闭包提供了询问自己参数个数的方法,无论在闭包内或者闭包外
c= {x,y,z-> getMaximumNumberOfParameters() }
assert c.getMaximumNumberOfParameters() == 3
assert c(4,5,6) == 3
2.2. 隐式参数
当一个闭包没有明确定义一个参数列表(使用 - >)时,闭包总是定义一个隐式参数,并将其命名为it
。
def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
等同于
def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
如果想声明一个不接受参数的闭包,并且必须限制为没有参数的调用,那么必须声明一个明确的空参数列表:
def magicNumber = { -> 42 }
// this call will fail because the closure doesn't accept any argument
magicNumber(11)
闭包的一些快捷写法,当闭包作为闭包或方法的最后一个参数。可以将闭包从参数圆括号中提取出来接在最后,如果闭包是唯一的一个参数,则闭包或方法参数所在的圆括号也可以省略。对于有多个闭包参数的,只要是在参数声明最后的,均可以按上述方式省略。见代码:
def runTwice = { a, c -> c(c(a)) }
assert runTwice( 5, {it * 3} ) == 45 //usual syntax
assert runTwice( 5 ){it * 3} == 45
//when closure is last param, can put it after the param list
def runTwiceAndConcat = { c -> c() + c() }
assert runTwiceAndConcat( { 'plate' } ) == 'plateplate' //usual syntax
assert runTwiceAndConcat(){ 'bowl' } == 'bowlbowl' //shortcut form
assert runTwiceAndConcat{ 'mug' } == 'mugmug'
//can skip parens altogether if closure is only param
def runTwoClosures = { a, c1, c2 -> c1(c2(a)) }
//when more than one closure as last params
assert runTwoClosures( 5, {it*3}, {it*4} ) == 60 //usual syntax
assert runTwoClosures( 5 ){it*3}{it*4} == 60 //shortcut form
2.3. 可变参数
闭包可以象任何其他方法一样声明可变参数
def concat1 = { String... args -> args.join('') }
assert concat1('abc','def') == 'abcdef'
def concat2 = { String[] args -> args.join('') }
assert concat2('abc', 'def') == 'abcdef'
def multiConcat = { int n, String... args ->
args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'
3. 委托策略
3.1. This owner delegate
Groovy的闭包比Java的Lambda表达式功能更强大。原因就是Groovy闭包可以修改委托对象和委托策略。这样Groovy就可以实现非常优美的领域描述语言(DSL)了。Gradle就是一个鲜明的例子
Groovy闭包有三个相关的对象
- this 即闭包定义所在的类。
- owner 即闭包定义所在的对象或闭包。
- delegate 即闭包中引用的第三方对象。
3.1.1 this
this代表闭包定义所在的类
在闭包中,调用getThisObject
将返回闭包定义的闭包类,和显式的调用this
一样
class Enclosing {
void run() {
def whatIsThisObject = { getThisObject() }
assert whatIsThisObject() == this
def whatIsThis = { this }
assert whatIsThis() == this
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { this }
}
void run() {
def inner = new Inner()
assert inner.cl() == inner
}
}
class NestedClosures {
void run() {
def close = {
def cl = { this }
cl()
}
assert nestedClosures() == this
}
}
在Inner中调用this
返回的是Inner这个类,而不是外层的EnclosedInInnerClass
在NestedClosures类中,cl定义在闭包close中,调用this返回最近的外层类NestedClosures,而不是闭包close
可以像下面这样调用封闭类的方法
class Person {
String name
int age
String toString() { "$name is $age years old" }
String dump() {
def cl = {
String msg = this.toString()
println msg
msg //返回值
}
cl()
}
}
def p = new Person(name:'Janice', age:74)
assert p.dump() == 'Janice is 74 years old'
3.1.2 Owner
Owner代表闭包定义所在的对象或闭包
class Enclosing {
void run() {
def whatIsOwnerMethod = { getOwner() }
assert whatIsOwnerMethod() == this
def whatIsOwner = { owner }
assert whatIsOwner() == this
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { owner }
}
void run() {
def inner = new Inner()
assert inner.cl() == inner
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { owner }
cl()
}
assert nestedClosures() == nestedClosures
}
}
3.1.3 Delegate of a closure
可以使用delegate
属性或者调用getDelegate
方法来访问闭包中的delegate
,delegate
在Groovy中是一个很重要的概念,delegate
需要我们手动指定
class Enclosing {
void run() {
def cl = { getDelegate() }
def cl2 = { delegate }
assert cl() == cl2()
assert cl() == this
def enclosed = {
{ -> delegate }.call()
}
assert enclosed() == enclosed
}
}
闭包的delegate
可以被改变成任何对象。 我们通过创建两个不是彼此的子类的类来说明这一点,但都定义了一个名为name的属性:
class Person {
String name
}
class Thing {
String name
}
def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')
然后定义一个闭包通过delegate
来调用name属性
def upperCasedName = { delegate.name.toUpperCase() }
然后通过更改闭包的委托,可以看到目标对象将会改变
upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'
上述行为与在闭包的词法范围中定义目标变量并没有区别
def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
assert upperCasedNameUsingVar() == 'NORMAN'
3.1.4 Delegation strategy(委托策略)
无论什么时候,在闭包中,访问某个属性时都没有明确地设置接收者对象,那么就会调用一个委托策略:
class Person {
String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() }
cl.delegate = p
//name property be resolved transparently on the delegate object!
assert cl() == 'IGOR'
调用name属性并没有执行是谁的name属性,然后把闭包cl的delegate设置为p
这里没有显式的给delegate设置一个接收者,但是能成功访问到name属性 因为相应的属性解析策略:
- Closure.OWNER_FIRST,默认策略,首先从owner上寻找属性或方法,找不到则在delegate上寻找。
- Closure.DELEGATE_FIRST,和上面相反。首先从delegate上寻找属性或者方法
- Closure.OWNER_ONLY,只在owner上寻找,delegate被忽略。
- Closure.DELEGATE_ONLY,和上面相反。只在delegate上寻找,owner被忽略。
- Closure.TO_SELF,高级选项,让开发者自定义策略。
看下面的例子:
class Person {
String name
def pretty = { "My name is $name" }
String toString() {
pretty()
}
}
class Thing {
String name
}
def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')
assert p.toString() == 'My name is Sarah'
p.pretty.delegate = t
assert p.toString() == 'My name is Sarah'
Person和Thing都定义了name属性,使用默认策略,首先会在Owner上寻找,所以即使我们把delegate换成t,输出结果还是一样的
下面改变一下委托策略
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'
把委托策略改成Closure.DELEGATE_FIRST,则会首先去delegate寻找name属性,如果没有找到,再去owner上寻找,但是delegate有name的定义,所以输出结果为“My name is Teapot”
delegate first
vs delegate only
(owner first
vsowner only
)
class Person {
String name
int age
def fetchAge = { age }
}
class Thing {
String name
}
def p = new Person(name:'Jessica', age:42)
def t = new Thing(name:'Printer')
def cl = p.fetchAge
cl.delegate = p
assert cl() == 42
cl.delegate = t
assert cl() == 42
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = p
assert cl() == 42
cl.delegate = t
try {
cl()
assert false
} catch (MissingPropertyException ex) {
// "age" is not defined on the delegate
}
only时,在对应的对象上找不到时,不会接着去别处找,而是抛出一个异常
4.函数式编程
待...
参考:
Groovy基础——Closure(闭包)详解
Closures