})
assert testStr == ‘12345678910’ //true
赋值方式声明闭包
def Closure getPrintCustomer() {
return { line -> println line }
}
def printCustomer = { line -> println line }
可以看到,无论如上哪种声明闭包方式,花括号都是其中的主角,而在 Groovy 中的花括号作用一般都是用来标明构建了一个新的闭包对象或者一个 Groovy 代码块。
-
Groovy 代码块:可以是类、接口、static、对象的初始化代码、方法体,或者与 Groovy 关键字(if、else、synchronzied、for、while、switch、try、catch 和 finally)一起出现的花括号。
-
一个新的闭包对象:除过 Groovy 代码块外的其它花括号的出现形式基本都是闭包。
同理,上面两种声明方式也能发现,闭包就是一个 Closure 对象实例,他们可以通过变量进行引用,能够作为参数传递,可以在闭包中调用方法,也可以作为一个方法的返回实例出现。
重用已有的方法声明闭包
一般我们的方法会有一个方法体,可选的返回值,能接受参数,并且能被调用。而通过上面两种闭包的声明和使用能发现,闭包的结构和特性和普通方法也很相似,因此 Groovy 可以让我们作为一个闭包重用已经在方法中存在的代码。
引用一个方法作为闭包是使用reference.&操作符
,reference
是闭包调用时使用的对象实例,就像普通方法调用 reference.someMethod()
一样。.&
可以被称为方法闭包操作符。
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
class MethodClosureTest {
int size;
MethodClosureTest(int size) {
this.size = size;
}
boolean validSize(String str) {
return str.length() >= size;
}
}
//使用
MethodClosureTest m1 = new MethodClosureTest(4);
Closure clo = m1.&validSize; //方法闭包
def li = [‘111’, ‘2’, ‘55555’];
li.find(clo); //‘55555’
可以看到,上面展示了传递闭包给方法执行的各种闭包声明方式,到此你至少能看懂闭包的声明和调用了,至于其闭包的原理我们下面接着看。
调用闭包
假设引用 closure 指向一个闭包,则可以通过closure.call([params])
或closure([params])
来调用闭包。
def test = { x, y = 5 ->
return x + y
}
//闭包调用
assert test(4, 3) == 7
assert test.call(4, 3) == 7
assert test(7) == 12
assert test.call(7) == 12
闭包语法的 class 代码
上面我们见证了闭包的声明和调用,为了循序渐进,我们这里先看看上面的代码的 class 字节码到底是啥样的,因为前面我们说过,Groovy 本质就是 JVM 语言,其编译后也是 class,只是 classPath 比 JDK 多了 GDK 而已。
//test.groovy
def testStr = ‘’
(1…10).each({
testStr += it
})
print(testStr)
如上代码使用groovyc test.groovy
编译产物为test.class
和test$_run_closure1.class
,_run_closure1 为 test 的内部类名,我们使用 IDEA 拖入这两个产物 class 文件查看反编译如下(这是反编译产物,实际可能有出入):
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.Reference;
import groovy.lang.Script;
import groovy.transform.Generated;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
//就是一个普通的 class 类,继承自 GDK 的 Script 类
public class test extends Script {
//父类的重写,不是我们关注重点,忽略
public test() {
CallSite[] var1 = $getCallSiteArray();
super();
}
//父类的重写,不是我们关注重点,忽略
public test(Binding context) {
CallSite[] var2 = $getCallSiteArray();
super(context);
}
/**
-
重点!!!
-
入口代码
*/
public static void main(String… args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].call(InvokerHelper.class, test.class, args);
}
public Object run() {
CallSite[] var1 = $getCallSiteArray();
//这就是我们定义的 testStr 接收字符串变量
final Reference testStr = new Reference(“”);
/**
- 闭包被转换为继承 Closure 的普通 java 类
*/
final class _run_closure1 extends Closure implements GeneratedClosure {
public _run_closure1(Object _outerInstance, Object _thisObject) {
CallSite[] var4 = $getCallSiteArray();
super(_outerInstance, _thisObject);
}
//闭包的调用本质调用的是这个方法,下面章节有源码解释
public Object doCall(Object it) {
CallSite[] var2 = $getCallSiteArray();
Object var10000 = var2[0].call(testStr.get(), it);
testStr.set(var10000);
return var10000;
}
public Object getTestStr() {
CallSite[] var1 = $getCallSiteArray();
return testStr.get();
}
@Generated
public Object doCall() {
CallSite[] var1 = $getCallSiteArray();
return this.doCall((Object)null);
}
}
//range的翻译
var1[1].call(ScriptBytecodeAdapter.createRange(1, 10, (boolean)1), new _run_closure1(this, this));
return var1[2].callCurrent(this, testStr.get());
}
}
上面代码不用看懂所有,也没有必要,你只要领悟精髓即可,即 Groovy 的一切都是对象,闭包的本质就是 Closure 实例对象即可,其他都可以理解成是 Java 的一种拓展,你甚至可以理解成他就是 Java 的一个三方框架,只是这个框架不仅仅提供了代码编写的规范,还有自己的一套构建规则,这才是本质思想。
闭包的委托策略
渐渐的我们慢慢揭开了闭包的面纱,Groovy 的闭包有委托的概念,即在闭包中修改委托对象和委托策略的能力,能够让它在 DSL(Domain Specific Languages)领域(譬如 Gradle 等)得到升华。理解闭包的委托功能,就需要先弄明白groovy.lang.Closure
类的三个属性,即 this、owner 和 delegate。
this、owner 和 delegate
this: 指向定义闭包所在的封闭类。在闭包中可以通过getThisObject()
方法获取定义闭包的封闭类,和显式调用 this 是等价的效果。这个不用再举例子了,比较简单,可以看下一小节的案例。
owner: 对应定义声明闭包的封闭对象,可以是类或者闭包。它与 this 有点类似,最大的不同是 owner 指向的是直接包裹它的对象(普通对象或者闭包对象)。这个不用再举例子了,比较简单,可以看下一小节的案例。
delegate: 可以对应任何对象。this 和 owner 都属于闭包的标准语法范围,而 delegate 指向的是闭包所使用的用户自定义的对象。默认情况下 delegate 被设置成 owner。我们可以通过 delegate 关键字和getDelegate()
、setDelegate(x)
方法来使用委托对象。样例如下:
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
class Animal {
String name
}
class Cat {
String name
}
def dog = new Animal(name: ‘dog’)
def cat = new Cat(name: ‘catter’)
def nameLength = { delegate.name.length() }
/**
-
默认 delegate 等价于 owner,即声明闭包所属的对象,此处为 Script
-
所以运行直接报错,因为 delegate 指向的对象没有 name 属性
*/
println(nameLength())
/**
-
闭包 delegate 指向 dog 对象
-
所以运行打印为 3
*/
nameLength.delegate = dog
println(nameLength())
/**
-
闭包 delegate 指向 cat 对象
-
所以运行打印为 6
*/
nameLength.delegate = cat
println(nameLength())
深入委托策略
在 Java 类的实例方法中调用类的方法和引用属性时,我们可以省略方法或属性前的this
(譬如this.func()
、this.property
可简写为func()
和property
),表示调用或引用的是本实例的方法或属性。
这个特性在 Groovy 中同样适用,而且 Groovy 在 闭包 Closure 中调用方法和引用属性时,我们也可以省略方法和属性前的delegate
(譬如delegate.func()
、delegate.property
可简写为func()
和property
),表示调用或引用的是本闭包的方法或属性(而闭包 Closure 通过 delegate 隐式变量将方法调用和变量引用委派给了 delegate 引用的那个对象)。
闭包 delegate 的默认值是 Closure 的隐式变量 owner,而 owner 通常对应定义声明闭包的封闭对象,可以是类或者闭包。因此,无论什么时候,在闭包中访问某个属性时如果没有明确地设置接收者对象,那么就会调用一个委托策略,譬如如下案例:
class ClosureTest {
String str1 = “1”
def outerClosure = {
def str2 = “2”
/**
-
属性或者方法存在于 owner 内,那么他可以被 owner 调用
-
所以 outerClosure 闭包的 owner 是其声明包裹的对象,即 test 实例
-
故 str1 = 1
*/
println str1
def nestedClosure = {
/**
-
属性或者方法不存在于自己的 owner 内,而自己的 owner 又是一个闭包实例对象,所以在自己 owner 的 owner 上继续寻找调用
-
故 str1 = 1
*/
println str1
/**
-
如果属性或者方法存在于 owner 内,那么他可以被 owner 调用
-
所以 nestedClosure 闭包的 owner 是其声明包裹的闭包对象,即 outerClosure 闭包实例
-
故 str2 = 2
*/
println str2
}
nestedClosure()
}
}
ClosureTest test = new ClosureTest()
def closure = test.outerClosure
closure()
/**
-
输出结果:
-
1
-
1
-
2
*/
上面例子中没有显式的给 delegate 设置一个接收者,但是无论哪层闭包都能成功访问到 str1、str2 值,这是因为默认的解析委托策略在发挥作用,Groovy 闭包的委托策略有如下几种:
-
Closure.OWNER_FIRST
:默认策略,首先从 owner 上寻找属性或方法,找不到则在 delegate 上寻找。 -
Closure.DELEGATE_FIRST
:和上面相反,首先从 delegate 上寻找属性或者方法,找不到则在 owner 上寻找。 -
Closure.OWNER_ONLY
:只在 owner 上寻找,delegate 被忽略。 -
Closure.DELEGATE_ONLY
:和上面相反,只在 delegate 上寻找,owner 被忽略。 -
Closure.TO_SELF
:高级选项,让开发者自定义策略,必须要自定义实现一个 Closure 类,一般我们这种玩家用不到。
下面是一些改变委托策略的样例,用来加深理解:
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
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
/**
-
p 的 owner 上有 name 属性,默认策略 Closure.OWNER_FIRST,
-
所以还是走的 p 的属性值。
*/
assert p.toString() == ‘My name is Sarah’
/**
-
p 的 owner 上有 name 属性,策略是 Closure.DELEGATE_FIRST,
-
所以和上面策略刚好相反,delegate 首先被委托,其次再委托给 owner,
-
所以走的是 t 的属性值。
*/
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == ‘My name is Teapot’
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
}
如上就是闭包的委托策略,请无比记住,非常重要,因为 Gradle 这种基于 Groovy 的 DSL 语言的精髓之一就是这个特性,我们会在后面文章中对 DSL 做专门的分析。
闭包机制本质反思
搞懂了 Groovy 的委托策略,接下来就该循序渐进的来复盘下闭包机制的本质,所以需要先思考类似下面一个典型场景的代码原理。
def sum = 0
5.times { sum++ }
assert sum == 5
可以看到,传递给 times 方法的闭包可以访问变量 sum,在声明闭包的时候本地变量是可以访问的。那执行闭包期间闭包是怎么访问 sum 变量的呢?
首先,times 方法每次回调时是没机会知道变量 sum 的,所以 times 方法不可能直接传递 sum 给闭包,也不知道闭包使用了 sum 变量,因为前面一再强调了,闭包其实是一种解耦能力,调用方是不关心其实现的。那要想在运行时获得 sum 变量,唯一的可能就是闭包在它的运行时生命周期里以某种方式记住了它的工作上下文环境,当调用它的时候,闭包可以在它的原始上下文中工作。这种生命周期的上下文需要闭包记住相应的一个引用,而不是一个复制对象实例,如果工作上下文是原始上下文的一个复制对象实例,则从闭包中是没有办法改变原始上下文的。
如上这个结论的猜想其实就是闭包的委托策略,你可以从闭包语法的 class 代码和闭包的委托策略两个小节就能悟到这个本质。
下面是一个典型案例(来自一本网络大佬的书籍实例):
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】
class Mather {
int field = 1
int foo() {
return 2
}
/**
-
指明这个方法将返回一个闭包对象
-
注意:在声明期间闭包的列表将被返回,闭包仍然没有被调用
*/
Closure birth(param) {
def local = 3
def closure = { caller ->
[this, field, foo(), local, param, caller, this.owner]
}
return closure
}
}
Mather julia = new Mather()
/**
-
使用一个新的变量接收闭包
-
注意:到现在 birth 返回的闭包还没有被调用,元素列表仍然还没有被构建
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
要如何成为Android架构师?
搭建自己的知识框架,全面提升自己的技术体系,并且往底层源码方向深入钻研。
大多数技术人喜欢用思维脑图来构建自己的知识体系,一目了然。这里给大家分享一份大厂主流的Android架构师技术体系,可以用来搭建自己的知识框架,或者查漏补缺;
对应这份技术大纲,我也整理了一套Android高级架构师完整系列的视频教程,主要针对3-5年Android开发经验以上,需要往高级架构师层次学习提升的同学,在这里点击GitHub免费分享,希望能帮你突破瓶颈,跳槽进大厂;
最后我必须强调几点:
1.搭建知识框架可不是说你整理好要学习的知识顺序,然后看一遍理解了能复制粘贴就够了,大多都是需要你自己读懂源码和原理,能自己手写出来的。
2.学习的时候你一定要多看多练几遍,把知识才吃透,还要记笔记,这些很重要! 最后你达到什么水平取决你消化了多少知识
3.最终你的知识框架应该是一个完善的,兼顾广度和深度的技术体系。然后经过多次项目实战积累经验,你才能达到高级架构师的层次。
你只需要按照在这个大的框架去填充自己,年薪40W一定不是终点,技术无止境
2)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-6gwKnHiR-1710662421233)]
要如何成为Android架构师?
搭建自己的知识框架,全面提升自己的技术体系,并且往底层源码方向深入钻研。
大多数技术人喜欢用思维脑图来构建自己的知识体系,一目了然。这里给大家分享一份大厂主流的Android架构师技术体系,可以用来搭建自己的知识框架,或者查漏补缺;
[外链图片转存中…(img-RuXgBXWB-1710662421233)]
对应这份技术大纲,我也整理了一套Android高级架构师完整系列的视频教程,主要针对3-5年Android开发经验以上,需要往高级架构师层次学习提升的同学,在这里点击GitHub免费分享,希望能帮你突破瓶颈,跳槽进大厂;
最后我必须强调几点:
1.搭建知识框架可不是说你整理好要学习的知识顺序,然后看一遍理解了能复制粘贴就够了,大多都是需要你自己读懂源码和原理,能自己手写出来的。
2.学习的时候你一定要多看多练几遍,把知识才吃透,还要记笔记,这些很重要! 最后你达到什么水平取决你消化了多少知识
3.最终你的知识框架应该是一个完善的,兼顾广度和深度的技术体系。然后经过多次项目实战积累经验,你才能达到高级架构师的层次。
你只需要按照在这个大的框架去填充自己,年薪40W一定不是终点,技术无止境