这个特性在 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 返回的闭包还没有被调用,元素列表仍然还没有被构建
*/
closure = julia.birth(4)
/**
-
显式调用 birth 返回的闭包
-
闭包从它出生的地方构建元素列表,我们将列表保存在一个变量 context 方便使用
-
注意我们把自身(本质是 Script 对象)作为参数传递给闭包,这样可以在闭包中作为 caller 使用
*/
context = closure.call(this)
/**
- 输出了这次运行的时候的脚本类的名称
*/
println context[0].class.name
/**
-
foo() 的调用其实是 this.foo() 的缩写
-
注意:this 指向引用到的闭包,而不是声明闭包的对象
-
在这里闭包将所有的方法调用代理给 delegate 对象,delegate 缺省是声明闭包的对象(也就是 owner),这样保证了闭包在它的上下文中运行。
*/
assert context[1…4] == [1, 2, 3, 4]
assert context[5] instanceof Script
/**
- 在闭包中,魔术变量 owner 引用到声明闭包的对象
*/
assert context[6] instanceof Mather
/**
-
每次调用 birth 方法的时候,闭包都重新构建
-
对于闭包来说,闭包的花括号就像 new 关键字一样
-
这就是闭包和方法的根本差别,方法在类产生的时候就被构建为仅有的一次,闭包对象在运行的时候被构建,并且相同的代码也有可能被构建多次。
*/
firstClosure = julia.birth(4)
secondClosure = julia.birth(4)
assert firstClosure.is(secondClosure) == false
怎么样,到此 Groovy 闭包委托代理的精髓就介绍完毕了,下面就是该揭开闭包本质的时候了。
闭包 Closure 类源码
Groovy 闭包的本质是一个 Closure 实例对象,GDK 里的 groovy.lang.Closure
是一个普通的 java 类,其内部有很多特性,具体感兴趣的可以去看下源码,如下是对源码部分删减的简介:
public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable, 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;
public static final int DONE = 1, SKIP = 2;
…
/**
- 可见每个闭包对象实例内部都有一个委托对象属性
*/
private Object delegate;
/**
- 可见每个闭包对象实例内部都有一个owner对象属性
*/
private Object owner;
/**
- 可见每个闭包对象实例内部都有一个this对象属性
*/
private Object thisObject;
/**
- 委托策略定义,默认是OWNER_FIRST
*/
private int resolveStrategy = OWNER_FIRST;
private int directive;
/**
- 闭包参数类型和个数相关属性
*/
protected Class[] parameterTypes;
protected int maximumNumberOfParameters;
private static final long serialVersionUID = 4368710879820278874L;
private BooleanClosureWrapper bcw;
/**
- 闭包构造方法,每个括号声明的闭包实例背后都是通过构造方法实例化的
*/
public Closure(Object owner, Object thisObject) {
this.owner = owner;
this.delegate = owner;
this.thisObject = thisObject;
final CachedClosureClass cachedClass = (CachedClosureClass) ReflectionCache.getCachedClass(getClass());
parameterTypes = cachedClass.getParameterTypes();
maximumNumberOfParameters = cachedClass.getMaximumNumberOfParameters();
}
…
/**
- 熟悉的委托策略设置
*/
public void setResolveStrategy(int resolveStrategy) {
this.resolveStrategy = resolveStrategy;
}
public int getResolveStrategy() {
return resolveStrategy;
}
/**
- 熟悉的闭包内部 this 获取方法
*/
public Object getThisObject(){
return thisObject;
}
/**
- 获取闭包属性,这就是一堆策略了,神马隐式简写本质都在这里体现了
*/
public Object getProperty(final String property) {
if (“delegate”.equals(property)) {
return getDelegate();
} else if (“owner”.equals(property)) {
return getOwner();
} else if (“maximumNumberOfParameters”.equals(property)) {
return getMaximumNumberOfParameters();
} else if (“parameterTypes”.equals(property)) {
return getParameterTypes();
} else if (“metaClass”.equals(property)) {
return getMetaClass();
} else if (“class”.equals(property)) {
return getClass();
} else if (“directive”.equals(property)) {
return getDirective();
} else if (“resolveStrategy”.equals(property)) {
return getResolveStrategy();
} else if (“thisObject”.equals(property)) {
return getThisObject();
} else {
switch(resolveStrategy) {
case DELEGATE_FIRST:
return getPropertyDelegateFirst(property);
case DELEGATE_ONLY:
return InvokerHelper.getProperty(this.delegate, property);
case OWNER_ONLY:
return InvokerHelper.getProperty(this.owner, property);
case TO_SELF:
return super.getProperty(property);
default:
return getPropertyOwnerFirst(property);
}
}
}
/**
- 一堆具体的委托策略获取查找实现
*/
private Object getPropertyDelegateFirst(String property) {
if (delegate == null) return getPropertyOwnerFirst(property);
return getPropertyTryThese(property, this.delegate, this.owner);
}
private Object getPropertyOwnerFirst(String property) {
return getPropertyTryThese(property, this.owner, this.delegate);
}
private Object getPropertyTryThese(String property, Object firstTry, Object secondTry) {
try {
// let’s try getting the property on the first object
return InvokerHelper.getProperty(firstTry, property);
} catch (MissingPropertyException e1) {
if (secondTry != null && firstTry != this && firstTry != secondTry) {
try {
// let’s try getting the property on the second object
return InvokerHelper.getProperty(secondTry, property);
} catch (GroovyRuntimeException e2) {
// ignore, we’ll throw e1
}
}
throw e1;
} catch (MissingFieldException e2) { // see GROOVY-5875
if (secondTry != null && firstTry != this && firstTry != secondTry) {
try {
// let’s try getting the property on the second object
return InvokerHelper.getProperty(secondTry, property);
} catch (GroovyRuntimeException e3) {
// ignore, we’ll throw e2
}
}
throw e2;
}
}
/**
- 设置闭包属性,这就是一堆策略了,神马隐式简写本质都在这里体现了
*/
public void setProperty(String property, Object newValue) {
if (“delegate”.equals(property)) {
setDelegate(newValue);
} else if (“metaClass”.equals(property)) {
setMetaClass((MetaClass) newValue);
} else if (“resolveStrategy”.equals(property)) {
setResolveStrategy(((Number) newValue).intValue());
} else if (“directive”.equals(property)) {
setDirective(((Number) newValue).intValue());
} else {
switch(resolveStrategy) {
case DELEGATE_FIRST:
setPropertyDelegateFirst(property, newValue);
break;
case DELEGATE_ONLY:
InvokerHelper.setProperty(this.delegate, property, newValue);
break;
case OWNER_ONLY:
InvokerHelper.setProperty(this.owner, property, newValue);
break;
case TO_SELF:
super.setProperty(property, newValue);
break;
default:
setPropertyOwnerFirst(property, newValue);
}
}
}
/**
- 一堆具体的委托策略设置查找机制
*/
private void setPropertyDelegateFirst(String property, Object newValue) {
if (delegate == null) setPropertyOwnerFirst(property, newValue);
else setPropertyTryThese(property, newValue, this.delegate, this.owner);
}
private void setPropertyOwnerFirst(String property, Object newValue) {
setPropertyTryThese(property, newValue, this.owner, this.delegate);
}
private void setPropertyTryThese(String property, Object newValue, Object firstTry, Object secondTry) {
try {
// let’s try setting the property on the first object
InvokerHelper.setProperty(firstTry, property, newValue);
} catch (GroovyRuntimeException e1) {
if (firstTry != null && firstTry != this && firstTry != secondTry) {
try {
// let’s try setting the property on the second object
InvokerHelper.setProperty(secondTry, property, newValue);
return;
} catch (GroovyRuntimeException e2) {
// ignore, we’ll throw e1
}
}
throw e1;
}
}
public boolean isCase(Object candidate){
if (bcw==null) {
bcw = new BooleanClosureWrapper(this);
}
return bcw.call(candidate);
}
…
/**
- 闭包的调用方法 call,可以传递参数
*/
public V call(Object… args) {
try {
return (V) getMetaClass().invokeMethod(this,“doCall”,args);
} catch (InvokerInvocationException e) {
ExceptionUtils.sneakyThrow(e.getCause());
return null; // unreachable statement
} catch (Exception e) {
return (V) throwRuntimeException(e);
}
}
…
/**
- 获取闭包内部的 owner 和 delegate
*/
public Object getOwner() {
return this.owner;
}
public Object getDelegate() {
return this.delegate;
}
public void setDelegate(Object delegate) {
this.delegate = delegate;
}
/**
-
依赖闭包是接受一个参数还是多个参数,通过闭包的这个方法能获取到期望的参数数量信息(类型,如果声明了类型)。
-
譬如调用闭包的前面先 closure.getParameterTypes().size() 获取参数个数再决定怎么调用等。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
更多Android高级工程师进阶学习资料
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
外链图片转存中…(img-QYpZEdRO-1711859486284)]
[外链图片转存中…(img-jDOPkhZH-1711859486285)]
[外链图片转存中…(img-ys9UC0Mw-1711859486285)]
[外链图片转存中…(img-yZ6VInae-1711859486285)]
[外链图片转存中…(img-iflxxkiE-1711859486286)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
更多Android高级工程师进阶学习资料
进阶学习视频
[外链图片转存中…(img-P4heNUMx-1711859486286)]
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-9mAunswh-1711859486286)]
里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…