注册

"Gradle"系列: 一、Gradle相关概念理解,Groovy基础(3)

四、Groovy 闭包


在 Groovy 中,闭包非常的重要,因此单独用一个模块来讲


1、闭包定义


引用 Groovy 官方对闭包的定义:A closure in Groovy is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable. 翻译过来就是:Groovy 中的闭包是一个开放的、匿名的代码块,它可以接受参数、返回值并将值赋给变量。 通俗的讲,闭包可以作为方法的参数和返回值,也可以作为一个变量而存在,闭包本质上就是一段代码块,下面我们就由浅入深的来学习闭包


2、闭包声明


1、闭包基本的语法结构:外面一对大括号,接着是申明参数,参数类型可省略,在是一个 -> 箭头号,最后就是闭包体里面的内容


2、闭包也可以不定义参数,如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫 it

//1
{ params ->
//do something
}

//2
{
//do something
}

3、闭包调用


1、闭包可以通过 .call 方法来调用


2、闭包可以直接用括号+参数来调用

//定义一个闭包赋值给 closure 变量
def closure = { params1,params2 ->
params1 + params2
}

//闭包调用方式1: 闭包可以通过 .call 方法来调用
def result1 = closure('erdai ','666')
//闭包调用方式2: 闭包可以直接用括号+参数来调用
def result2 = closure.call('erdai ','777')
//打印值
println result1
println result2
//打印结果
erdai 666
erdai 777

//定义一个无参闭包
def closure1 = {
println('无定义参数闭包')
}
closure1() //或者调用 closure1.call()
//打印结果
无定义参数闭包

4、闭包进阶


1)、闭包中的关键变量


每个闭包中都含有 this、owner 和 delegate 这三个内置对象,那么这三个三个内置对象有啥区别呢?我们用代码去验证一下


注意


1、getThisObject() 方法 和 thisObject 属性等同于 this


2、getOwner() 方法 等同于 owner


3、getDelegate() 方法 等同于 delegate


这些去看闭包的源码你就会有深刻的体会


1、我们在 GroovyGrammar.groovy 这个脚本类中定义一个闭包打印这三者的值看一下:

//定义一个闭包
def outerClosure = {
println "this: " + this
println "owner: " + owner
println "delegate: " + delegate
}
//调用闭包
outerClosure.call()
//打印结果
this: variable.GroovyGrammar@39dcf4b0
owner: variable.GroovyGrammar@39dcf4b0
delegate: variable.GroovyGrammar@39dcf4b0
//证明当前三者都指向了GroovyGrammar这个脚本类对象

2、我们在这个 GroovyGrammar.groovy 这个脚本类中定义一个类,类中定义一个闭包,打印看下结果:

//定义一个 OuterClass 类
class OuterClass {
//定义一个闭包
def outerClosure = {
println "this: " + this
println "owner: " + owner
println "delegate: " + delegate
}
}

def outerClass = new OuterClass()
outerClass.outerClosure.call()

//打印结果如下:
this: variable.OuterClass@1992eaf4
owner: variable.OuterClass@1992eaf4
delegate: variable.OuterClass@1992eaf4
//结果证明这三者都指向了当前 OuterClass 类对象

3、我们在 GroovyGrammar.groovy 这个脚本类中,定义一个闭包,闭包中在定义一个闭包,打印看下结果:

def outerClosure = {
def innerClosure = {
println "this: " + this
println "owner: " + owner
println "delegate: " + delegate
}
innerClosure.call()

}
println outerClosure
outerClosure.call()

//打印结果如下
variable.GroovyGrammar$_run_closure4@64beebb7
this: variable.GroovyGrammar@5b58ed3c
owner: variable.GroovyGrammar$_run_closure4@64beebb7
delegate: variable.GroovyGrammar$_run_closure4@64beebb7
//结果证明 this 指向了当前GroovyGrammar这个脚本类对象 owner 和 delegate 都指向了 outerClosure 闭包对象

我们梳理一下上面的三种情况:


1、闭包定义在GroovyGrammar.groovy 这个脚本类中 this owner delegate 就指向这个脚本类对象


2、我在这个脚本类中创建了一个 OuterClass 类,并在他里面定义了一个闭包,那么此时 this owner delegate 就指向了 OuterClass 这个类对象


3、我在 GroovyGrammar.groovy 这个脚本类中定义了一个闭包,闭包中又定义了一个闭包,this 指向了当前GroovyGrammar这个脚本类对象, owner 和 delegate 都指向了 outerClosure 闭包对象


因此我们可以得到结论:


1、this 永远指向定义该闭包最近的类对象,就近原则,定义闭包时,哪个类离的最近就指向哪个,我这里的离得近是指定义闭包的这个类,包含内部类


2、owner 永远指向定义该闭包的类对象或者闭包对象,顾名思义,闭包只能定义在类中或者闭包中


3、delegate 和 owner 是一样的,我们在闭包的源码中可以看到,owner 会把自己的值赋给 delegate,但同时 delegate 也可以赋其他值


注意:在我们使用 this , owner , 和 delegate 的时候, this 和 owner 默认是只读的,我们外部修改不了它,这点在源码中也有体现,但是可以对 delegate 进行操作


2)、闭包委托策略


下面我们就来对修改闭包的 delegate 进行实操:

//创建一个香蕉类
class Banana{
def name
}

//创建一个橘子类
class Orange{
def name
}

//定义一个香蕉对象
def banana = new Orange(name: '香蕉')
//定义一个橘子对象
def orange = new Orange(name: '橘子')
//定义一个闭包对象
def closure = {
//打印值
println delegate.name
}
//调用闭包
closure.call()

//运行一下,发现结果报错了,如下
Caught: groovy.lang.MissingPropertyException: No such property: name for class: variable.GroovyGrammar
//大致意思就是GroovyGrammar这个脚本类对象没有这个 name 对象

我们来分析下报错的原因原因,分析之前我们要明白一个知识点:


闭包的默认委托策略是 OWNER_FIRST,也就是闭包会先从 owner 上寻找属性或方法,找不到则在 delegate 上寻找


1、closure 这个闭包是生明在 GroovyGrammar 这个脚本类当中


2、根据我们之前学的知识,在不改变 delegate 的情况下 delegate 和 owner 是一样的,都会指向 GroovyGrammar 这个脚本类对象


3、GroovyGrammar 这个脚本类对象,根据闭包默认委托策略,找不到 name 这个属性


因此报错了,知道了报错原因,那我们就修改一下闭包的 delegate , 还是上面那段代码,添加如下这句代码:

//修改闭包的delegate
closure.delegate = orange
//我们在运行一下,打印结果:
橘子

此时闭包的 delegate 指向了 orange ,因此会打印 orange 这个对象的 name ,那么我们把 closure 的 delegate 改为 banana,肯定就会打印香蕉了

//修改闭包的delegate
closure.delegate = banana
//我们在运行一下,打印结果:
香蕉

3)、深入闭包委托策略

//定义一个 ClosureDepth 类
class ClosureDepth{
//定义一个变量 str1 赋值为 erdai666
def str1 = 'erdai666'
//定义一个闭包
def outerClosure = {
//定义一个变量 str2 赋值为 erdai777
def str2 = 'erdai777'
//打印str1 分析1
println str1

//闭包中在定义一个闭包
def innerClosure = {
//分析2
println str1
println str2
}
//调用内部这个闭包
innerClosure.call()
}
}

//创建 ClosureDepth 对象
def closureDepth = new ClosureDepth()
//调用外部闭包
closureDepth.outerClosure.call()
//运行程序,打印结果如下
erdai666
erdai666
erdai777

上面代码注释写的很清楚,现在我们来重点分析下分析1和分析2处的打印值:


分析1:


分析1处打印了 str1 , 它处于 outerClosure 这个闭包中,此时 outerClosure 这个闭包的 owner , delegate 都指向了 ClosureDepth 这个类对象,因此 ClosureDepth 这个类对象的属性和方法我们就都能调用到,因此分析1处会打印 erdai666


分析2:


分析2处打印了 str1和 str2,它处于 innerClosure 这个闭包中,此时 innerClosure 这个闭包的 owner 和 delegate 会指向 outerClosure 这个闭包对象,我们会发现 outerClosure 有 str2 这个属性,但是并没有 str1 这个属性,因此 outerClosure 这个闭包会向它的 owner 去寻找,因此会找到 ClosureDepth 这个类对象的 str1 属性,因此打印的 str1 是ClosureDepth 这个类对象中的属性,打印的 str2 是outerClosure 这个闭包中的属性,所以分析2处的打印结果分别是 erdai666 erdai777


上面的例子中没有显式的给 delegate 设置一个接收者,但是无论哪层闭包都能成功访问到 str1、str2 值,这是因为默认的解析委托策略在发挥作用,Groovy 闭包的委托策略有如下几种:




  1. OWNER_FIRST:默认策略,首先从 owner 上寻找属性或方法,找不到则在 delegate 上寻找

  2. DELEGATE_FIRST:和上面相反,首先从 delegate 上寻找属性或者方法,找不到则在 owner 上寻找

  3. OWNER_ONLY:只在 owner 上寻找,delegate 被忽略

  4. DELEGATE_ONLY:和上面相反,只在 delegate 上寻找,owner 被忽略

  5. TO_SELF:高级选项,让开发者自定义策略,必须要自定义实现一个 Closure 类,一般我们这种玩家用不到

下面我们就来修改一下闭包的委托策略,加深理解:

class People1{
def name = '我是People1'

def action(){
println '吃饭'
}

def closure = {
println name
action()
}
}

class People2{
def name = '我是People2'

def action(){
println '睡觉'
}
}

def people1 = new People1()
def people2 = new People2()
people1.closure.delegate = people2
people1.closure.call()
//运行下程序,打印结果如下:
我是People1
吃饭

what? 这是啥情况,我不是修改了 delegate 为 people2 了,怎么打印结果还是 people1 的?那是因为我们忽略了一个点,没有修改闭包委托策略,他默认是 OWNER_FIRST ,因此我们修改一下就好了,还是上面这段代码,添加一句代码如下:

people1.closure.resolveStrategy = Closure.DELEGATE_FIRST
//运行下程序,打印结果如下:
我是People2
睡觉

到这里,相信你对闭包了解的差不多了,下面我们在看下闭包的源码就完美了


4)、闭包 Closure 类源码


仅贴出关键源码

public abstract class Closure<V> extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable<V>, Serializable {
/**
* 熟悉的一堆闭包委托代理策略
*/
public static final int OWNER_FIRST = 0;
public static final int DELEGATE_FIRST = 1;
public static final int OWNER_ONLY = 2;
public static final int DELEGATE_ONLY = 3;
public static final int TO_SELF = 4;
/**
* 闭包对应的三个委托对象 thisObject 对应的就是 this 属性,都是用 private 修饰的,外界访问不到
*/
private Object delegate;
private Object owner;
private Object thisObject;
/**
* 闭包委托策略
*/
private int resolveStrategy;

/**
* 在闭包的构造方法中:
* 1、将 resolveStrategy 赋值为0,也是就默认委托策略OWNER_FIRST
* 2、thisObject ,owner ,delegate都会被赋值,delegate 赋的是 owner的值
*/
public Closure(Object owner, Object thisObject) {
this.resolveStrategy = 0;
this.owner = owner;
this.delegate = owner;
this.thisObject = thisObject;
CachedClosureClass cachedClass = (CachedClosureClass)ReflectionCache.getCachedClass(this.getClass());
this.parameterTypes = cachedClass.getParameterTypes();
this.maximumNumberOfParameters = cachedClass.getMaximumNumberOfParameters();
}

/**
* thisObject 只提供了 get 方法,且 thisObject 是用 private 修饰的,因此 thisObject 即 this 只读
*/
public Object getThisObject() {
return this.thisObject;
}

/**
* owner 只提供了 get 方法,且 owner 是用 private 修饰的,因此 owner 只读
*/
public Object getOwner() {
return this.owner;
}

/**
* delegate 提供了 get 和 set 方法,因此 delegate 可读写
*/
public Object getDelegate() {
return this.delegate;
}

public void setDelegate(Object delegate) {
this.delegate = delegate;
}

/**
* 熟悉的委托策略设置
*/
public void setResolveStrategy(int resolveStrategy) {
this.resolveStrategy = resolveStrategy;
}
public int getResolveStrategy() {
return resolveStrategy;
}
}

到这里闭包相关的知识点就都讲完了,但是,但是,但是,重要的事情说三遍:我们使用闭包的时候,如何去确定闭包的参数呢?,这个真的很蛋疼,作为 Android 开发者,在使用 AndroidStudio 进行 Gradle 脚本编写的时候,真的是非常不友好,上面我讲了可以使用一个小技巧去解决这个问题,但是这种情况是在你知道要使用一个 Api 的情况下,比如你知道 Map 的 each 方法可以遍历,但是你不知道参数,这个时候就可以去使用。那如果你连 Api 都不知道使用,那就更加不知道闭包的参数了,因此要解决这种情况,我们就必须去查阅 Groovy 官方文档:


http://www.groovy-lang.org/api.html


http://docs.groovy-lang.org/latest/html/groovy-jdk/index-all.html



作者:妖孽那里逃
链接:https://www.jianshu.com/p/124effa509bb
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1 个评论

系列文章很好,都是精髓

要回复文章请先登录注册