Groovy的闭包相当于一个匿名代码块,可以接受参数,返回值,也可以当做变量来使用。闭包可以引用它外围的变量,但是外围不能引用闭包内的变量,这好像是破坏代码结构的做法,但是也有其相对的好处。
1.语法
1.1 定义闭包
闭包的语法公式:{[ 参数 ->] 语句/返回值}
参数:可以是0到多个有类型或不声明类型的参数,用“->”和语句隔开
下面是一些例子:
{ item++ } 这里只有一个返回值
{ -> item++ } 这里是表明必须无参
{ println it } it在没有声明的情况下为默认参数
{ it -> println it } 作用等同上一句
{ name -> println name } neme为无类型参数
{ String x, int y ->
println "hey ${x} the value is ${y}" 两个有类型参数传入
}
{ reader ->
def line = reader.readLine() 虽然无类型声明,但可以直接调用其函数
line.trim()
}
1.2 闭包做为一个对象
闭包其实是代表groovy.long.Closure类,但是闭包的形式可以方便的赋值给其他变量。
def listener = { e -> println "Clicked on $e.source" } 分配listener为一个Closure实例
assert listener instanceof Closure
Closure callback = { println 'Done!' } 显式赋值
Closure<Boolean> isTextFile = {
File it -> it.name.endsWith('.txt') 显式指定返回值
}
1.3 调用闭包
闭包可以像方法一样调用,无参情况直接用: 闭包名()可调用,或用 闭包名.call()调用,传参则放入 () 内。下面是例子:
def code = { 123 }
assert code() == 123
assert code.call() == 123
def isOdd = { int i-> i%2 == 1 }
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
2.2 隐藏参数
Groovy闭包不显式声明参数列表的话,默认使用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)
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 闭包和lambda表达式
Groovy的闭包和Java8的lambda表达式是非常不同的,代理策略是一个表现和lambda表达式不同的关键概念。改变代理指向不同对象和不同的代理策略可以使Groovy构建优雅的DSL。
3.2 Owner,delegate,this
这三个概念是闭包的难点和核心,是闭包难理解的关键。
this
指向定义该闭包的类(其实这里说的类只是为了排除闭包)owner
指向定义该闭包的对象(这里的对象意思即:可能是类或闭包)delegate
指向调用这个闭包的对象(默认策略下等同于owner) 或or properties are resolved whenever the receiver of the message is not defined(不会翻译这一句)。
3.2.1 this
其实上面对于概念的介绍已经算是清楚,下面主要看例子:
class Enclosing {
void run() {
def whatIsThisObject = { getThisObject() } getThisObject()等同于得到闭包的this 或 等同于引用闭包外围的this变量
assert whatIsThisObject() == this 这里证明闭包的this==其外围class的this (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 nestedClosures = {
def cl = { this }
cl()
}
assert nestedClosures() == this 这里即可排除指向闭包
}
}
利用闭包的这个特性的例子:
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.2.2 Owner
Owner和this非常像,区别是Owner指向离它最近的闭合对象,所以可能是class也可能是闭包。
class Enclosing {
void run() {
def whatIsOwnerMethod = { getOwner() } getOwner()即得到闭包的Owner
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 这里就是区别,上面的例子都是和this相同的
}
}
3.2.3 Delegate
Delegate可以用delegate属性取到,或用getDelegate方法,this和owner都是指向特定的范围,delegate是可以设定指向谁,这是groovy可以方便设计DSL的核心特性。delegate默认指向owner。
class Enclosing {
void run() {
def cl = { getDelegate() }
def cl2 = { delegate }
assert cl() == cl2()
assert cl() == this
def enclosed = {
{ -> delegate }.call()
}
assert enclosed() == enclosed
}
}
delegate可以指向任何对象,下面用两个类来做例子:
class Person {
String name
}
class Thing {
String name
}
def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot') 定义两个对象
def upperCasedName = { delegate.name.toUpperCase() } 定义一个闭包,注意并未定义在任一个类里面,而是单独在外面的
upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN' 闭包的delegate指向p后,结果就是这样的
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT' 闭包的delegate指向t后,结果变成这样
这可能有点像引用了一个变量而已,比如这样:
def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
assert upperCasedNameUsingVar() == 'NORMAN'
但是有两点不同
- target是闭包引用的外界的一个本地变量
- 对于delegate闭包可以直接显式调用,即:闭包名.delegate.变量
就行
3.2.4 代理策略
当一个闭包不定义在任何闭合环境内,把delegate指向谁,这个闭包中调用的属性或方法就指向delegate指向的对象。这是delegate在默认策略下的作用。
class Person {
String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() } 这里name不知道到哪去找
cl.delegate = p 但是这里delegate指向了p,name就去p里找
assert cl() == 'IGOR'
代理策略是指,找一个闭包内的属性或方法的方案,即,闭包内的属性或方法实际该指向谁。以下是这几种策略:
- Closure.OWNER_FIRST
这是默认策略,先在owner上找,没有的话,在delegate上找
- Closure.DELEGATE_FIRST
和上面相反
- Closure.OWNER_ONLY
只在owner上找,没有就报错
- Closure.DELEGATE_ONLY
和上面类似
- Closure.TO_SELF
如果你实现了自己的Closure类,这个就适用,这里执行你的自定义策略
下面举例子:
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' 默认策略,先找owner
如果改一下策略:
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot' 先找delegate上的
resolveStrategy用来改策略的
下面再举例子:
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
}
4. 闭包在GString上的应用
先看例子:
def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 2' 这里返回false
上面把x=1先赋值给了gs,从这里gs就生成了对象,这里就固定死了是1,如果x是个类对象,那当这个对象的toString方法返回值改变时,gs是会变的。即,gs生成时绑定了谁,那就是谁了,每次gs就读取这个对象的toString方法来赋值自己,这个对象内部可以变,但是gs不会再指向其他对象。所以如果想让gs跟着x变,就这么写 −>x而不是 {x},后者相当于$x。而前者是一个闭包,相当于一个类对象。
def x = 1
def gs = "x = ${-> x}" 这里绑定了${-> x}这个对象,而不是x
assert gs == 'x = 1'
x = 2 ${-> x}这个对象的x属性变了
assert gs == 'x = 2'
再看两个对象的例子:
class Person {
String name
String toString() { name }
}
def sam = new Person(name:'Sam')
def lucy = new Person(name:'Lucy')
def p = sam
def gs = "Name: ${p}"
assert gs == 'Name: Sam'
p = lucy p指向了lucy,但是gs里绑定的是sam
assert gs == 'Name: Sam'
sam.name = 'Lucy'
assert gs == 'Name: Lucy'
同样的,要想gs跟着p变:
class Person {
String name
String toString() { name }
}
def sam = new Person(name:'Sam')
def lucy = new Person(name:'Lucy')
def p = sam
// Create a GString with lazy evaluation of "p"
def gs = "Name: ${-> p}" 这里绑定了${-> p}这个对象,而不是p
assert gs == 'Name: Sam'
p = lucy ${-> p}这个对象的p属性变了
assert gs == 'Name: Lucy'
5. 闭包强制类型转换
待续。。