1. 介绍
本篇内容为Groovy知识学习第27篇,接着上一篇介绍的闭包知识进行深入学习。
上一篇介绍了如何创建闭包,如何使用闭包,如何给闭包进行传参。
这一篇开始学习闭包的授权策略等知识点。例如和lambda的区别,闭包的委托delegate,所有owner等作用。
2. 授权策略
2.1 闭包与 lambda表达式
Groovy将闭包定义为Closure
类的实例。这使得它与Java 8中的lambda
表达式非常不同。委托是Groovy闭包中的一个关键概念,在lambdas中没有对应的概念。更改委托或更改闭包的委托策略的能力使在Groovy中设计漂亮的领域特定语言(dsl)成为可能。
所以,我们不能单纯的将闭包理解为lambda表达式。因为这是两种不同的东西。
2.2 所有者Owner,委托delegate和this对象
要理解委托的概念,我们必须首先解释this
在闭包中的含义。闭包实际上定义了3个不同的东西:
this
:对应于定义闭包的封闭类owner
:所有者,对应于定义闭包的封闭对象,该对象可以是类或闭包。delegate
:委托,对应于第三方对象,在该对象中,每当未定义消息的接收者时,就解析方法调用或属性。
下面来详细介绍这中间的区别。
2.2.1 闭包中的this
在闭包中,调用getThisObject
将返回定义闭包的外围类。它等价于显式使用this
。具体示例如下所示:
//创建igeEnclosing 的类
class Enclosing {
void run() {
//定义了一个闭包对象,返回值为 getThisObject()方法的结果值
def whatIsThisObject = {
getThisObject()
}
println (whatIsThisObject == this) //输出:false
//定义了一个闭包对象,返回值为this
def whatIsThis = { this }
println (whatIsThis == this) //输出: false
}
}
//创建一个EnclosedInInnerClass 的类
class EnclosedInInnerClass {
//创建一个内部类
class Inner {
//创建一个闭包对象,我们讲过,所有的闭包对象都是Closure对象。
Closure cl = { this } //返回的this ,就是Inner这个内部类对象
}
void run() {
// 创建一个Inner对象
def inner = new Inner()
println inner.cl == inner //输出: false
}
}
//创建一个NestedClosures 的类
class NestedClosures {
void run() {
//创建一个闭包对象
def nestedClosures = {
//闭包对象中再创建一个闭包对象
def cl = { this } //返回当前闭包对象的this 值
cl()
}
//最终返回这个子闭包对象,其实就是this值,这对应的是最接近的外部类,而不是封闭闭包!
println nestedClosures() == this // 输出: true
}
}
def x = new Enclosing()
x.run()
def z = new EnclosedInInnerClass()
z.run()
def y = new NestedClosures()
y.run()
通过注释中的输出结果,我们可以理解了在闭包中的this 对象到底是什么
当然可以这样调用外围类中的方法:
class Person {
String name
int age
String toString() { "$name is $age years old" }
String dump() {
def cl = {
//闭包在this上调用toString,它实际上会在封闭对象上调用toString方法,也就是说Person实例
String msg = this.toString()
println msg
msg
}
cl()
}
}
def p = new Person(name:'zinyan', age:3)
p.dump() //输出:zinyan is 3 years old
2.2.2 闭包中的owner
闭包的所有者与闭包中this的定义非常相似,但有细微的区别:它将返回直接的封闭对象,无论是闭包还是类
。示例如下:
//创建一个Enclosing类
class Enclosing {
void run() {
//定义一个返回getOwner()的闭包对象
def whatIsOwnerMethod = { getOwner() }
println whatIsOwnerMethod() == this //输出: true
//定义一个直接返回owner的闭包对象
def whatIsOwner = { owner }
println whatIsOwner() == this //输出: true
}
}
class EnclosedInInnerClass {
class Inner { //定义一个内部类,然后创建一个闭包属性, 该值为owner
Closure cl = { owner }
}
void run() {
def inner = new Inner()
println inner.cl() == inner //输出 true
}
}
class NestedClosures {
void run() {
//定义一个多层闭包对象,返回的值owner
def nestedClosures = {
def cl = { owner }
cl()
}
println nestedClosures() == nestedClosures //输出 true
}
}
new Enclosing().run()
new EnclosedInInnerClass().run()
new NestedClosures().run()
通过上的示例中的结果,可以看到getOwner
和owner
返回的是一个对象,它返回的就是封闭对象本身。
2.2.3 闭包中的Delegate
可以通过使用delegate
属性或调用getDelegate
方法访问闭包的委托。它是在Groovy中构建领域特定语言的一个强大概念。虽然this
和owner
引用闭包的词法作用域,但委托是闭包将使用的用户定义对象。默认情况下,委托被设置为owner
。示例如下:
class Enclosing {
void run() {
//创建一个闭包对象,并返回委托
def zin = { getDelegate() }
//创建一个闭包对象,并返回委托,和上面的等效
def yan = { delegate }
//
println zin()==yan //输出: false
println yan() == this //输出: true
def enclosed = {
{ -> delegate }.call()
}
println enclosed()== enclosed //输出: true
}
}
def test =new Enclosing()
test.run()
闭包的委托可以更改为任何对象。让我们通过创建两个类来说明这一点,它们彼此不是子类,但都定义了一个名为name的属性:
//创建两个类, 它们都有一个属性 叫做name的String值。
class Person {
String name
}
class Thing {
String name
}
//创建两个实体对象
def p = new Person(name: 'zinyan')
def t = new Thing(name: 'z同学')
//然后让我们定义一个闭包来获取委托上的name属性:
def upperCasedName = { delegate.name.toUpperCase() }
//然后通过改变闭包的委托,可以看到目标对象会发生变化:
//更改闭包对象的delegate为 实例p
upperCasedName.delegate = p
println upperCasedName() //输出:ZINYAN
//更改闭包对象的delegate为实例t
upperCasedName.delegate = t
println upperCasedName() //输出:Z同学
此时,该行为与在闭包的词法作用域中定义一个目标变量没有区别:
def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
println upperCasedNameUsingVar() //输出 :ZINYAN
而使用delegate和直接传参的区别有以下两点:
- 在最后一个示例中,
target
是从闭包中引用的一个局部变量. - 可以透明地使用委托,也就是说不需要用
delegate.
为方法调用添加前缀。
this
和owner
比较好理解,下面主要介绍delegate
的情况
2.2.4 委托的策略
在闭包中,只要访问属性而没有显式设置接收对象,就会涉及到委托策略:
class Person {
String name
}
def p = new Person(name:'zinyan.com')
//name没有引用闭包的词法作用域中的变量
def cl = { name.toUpperCase() }
//我们可以将闭包的委托更改为Person的实例
cl.delegate = p
println cl() //输出:ZINYAN.COM
上面这段代码工作的原因是name
属性将在delegate对象上透明解析!这是在闭包内部解析属性或方法调用的一种非常强大的方法。不需要设置显式delegate。
闭包实际上定义了多种解析策略,你可以选择:
Closure.OWNER_FIRST
:为默认策略,owner所有者优先。如果一个属性/方法存在于owner
上,那么它将在所有者上被调用。如果没有,则使用delegate
。Closure.OWNER_ONLY
:将只解析所有者(owner
)上的属性/方法查找:委托(delegate
)将被忽略。Closure.DELEGATE_FIRST
与Closure.OWNER_FIRST
逻辑相反,delegate
委托优先,先使用delegate
,没有才会使用owner
。Closure.DELEGATE_ONLY
:将只解析委托(delegate
)上的属性/方法查找:所有者(owner
)将被忽略。Closure.TO_SELF
:可以供需要高级元编程技术并希望实现自定义解析策略的开发人员使用:解析不会在所有者或委托上进行,而只在闭包类本身上进行。只有在实现自己的Closure
子类时才有意义。
下面通过示例,来理解上的各种解析策略。
class Person {
String name
def pretty = { "我的名字叫做: $name" } //创建一个应用了name变量的闭包对象
//定义一个方法,返回pretty闭包对象的值。
String toString() {
pretty()
}
}
class Thing {
String name
}
def zin =new Person(name:'zinyan.com')
println zin.toString() //输出:我的名字叫做: zinyan.com
def yan = new Thing(name:'Z同学')
zin.pretty.delegate = yan //我们把委托改成yan 对象
println zin.toString() //输出:我的名字叫做: zinyan.com
可以看到。上面的闭包采用了默认的Closure.OWNER_FIRST
策略。我们修改delegate
并不影响最终的输出结果。因为owner
对象已经有name
的值了。
当我们进行策略修改后:
def zin =new Person(name:'zinyan.com')
println zin.toString() //输出:我的名字叫做: zinyan.com
def yan = new Thing(name:'Z同学')
zin.pretty.delegate = yan //我们把委托改成yan 对象
//修改策略
zin.pretty.resolveStrategy = Closure.DELEGATE_FIRST
println zin.toString() //输出:我的名字叫做: Z同学
当我们通过resolveStrategy
进行修改策略后,输出的结果就变了。因为闭包对象将会先从delegate
变量中取值。
上面的示例是介绍了,如果两者都有属性或方法时,委托优先还是所有者优先的。那么如果其中一个并没有属性时会是什么情况呢。
示例如下:
class Person {
String name
int age=10
//创建了一个闭包对象,它使用了age变量,
def fetchAge = { age }
}
//这个对象中 并没有age变量,而是只有name
class Thing {
String name
}
//创建两个对象
def p = new Person(name:'zinyan.com', age:1024)
def t = new Thing(name:'Z同学')
//获取person对象中的闭包对象。
def cl = p.fetchAge
cl.delegate = p //将闭包对象的delegate委托修改为p值
println cl() //输出: 1024
cl.delegate = t //将闭包对象的delegate修改为t值
println cl() // 输出: 1024
//修改策略, 仅支持委托
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = p
println cl() // 输出: 1024
cl.delegate = t // 请注意t 是没有age变量的
try {
println cl()
} catch (MissingPropertyException ex) {
println "age变量比存在,出现了异常" //输出:age变量比存在,出现了异常
}
可以看到,如果更改策略后,该策略支持的ower
或delegate
对象不存在闭包需要的属性时就会出现异常了。
2.2.5 元编程下的委派策略
在描述“所有者优先”委托策略时,我们谈到如果所有者的属性/方法“存在”,则使用来自委托的各自的属性/方法。“委托优先”的情况类似,但相反。与其用“存在”这个词,不如用“处理过”这个词更准确。这意味着对于“所有者优先”,如果属性/方法存在于所有者中,或者它有propertyMissing
/methodMissing
钩子,那么所有者将处理成员访问。
我们可以在前面的例子的一个稍微修改的版本中看到这一点:
class Person {
String name
int age
def fetchAge = { age }
}
class Thing {
String name
def propertyMissing(String name) { -1 }
}
def p = new Person(name:'zinyan.com', age:1024)
def t = new Thing(name:'Z同学')
def cl = p.fetchAge
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl.delegate = p
println cl() //输出:1024
cl.delegate = t
println cl() //输出: -1
在这个例子中,尽管我们的Thing
类实例(我们最后一次使用cl
的委托)没有age
属性,但它通过propertyMissing
钩子处理missing
属性的事实意味着age
将是-1。
所以不会触发崩溃异常, 最终输出-1值。
3. 小结
本章的重点,其实就在于闭包中的owner和delegate的区别以及它们的用处了。
同时我们可以通过resolveStrategy
手动修改闭包的默认策略。
相关知识可以通过Groovy官方文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_delegation_strategy
来了解更详细的。
下章继续分享关于闭包的相关知识。如果觉得总结的还可以,希望能够给我点个赞鼓励一下。谢谢。
评论区