工匠若水可能会迟到,但是从来不会缺席,最终还是觉得将自己的云笔记分享出来吧 ~
Groovy 是基于 JVM 的一种语言,也是 Java 的一种升级拓展语言。其具备脚本语言的特性,但又不算脚本,所以其运行有如下两种方式:
无论上面的哪种运行方式,其本质都是 JVM 字节码,可以理解成和 java 编译运行没啥区别,只是 groovy 有自己的编译器,通过 java 命令运行其预编译的字节码需要在 classpath 额外加上 groovy 自己特有的 GDK jar 包,这也和 java 的 JDK 类似。使用一张图可能更加直观,如下:
==
操作符不象 Java 其本质是调用了对象的 equals 方法进行比较,而不是判断引用是否相等。.
操作调用其 Integer 相关方法属性,特别注意1+2
这种操作在 Java 会被先计算,而在 Groovy 中由于都是对象,所以会被转换为对象 1 调用其plus(对象 2)
得到一个新对象。+
实际调用的是对象的plus(val)
方法;我们也可以重写这些运算符,本质就是重写plus(val)
方法,不像 Java 需要实现一个特定的接口。java.lang.String
的实例,GString 是groovy.lang.GString
的实例,GString 允许有占位符并且允许在运行时对占位符进行解析和计算。$
处理内容,其等价于 java 的字符串;双引号字符串内容中包含的$
符号按照 GString 实例特性处理;三组引号(或者是多行字符串)允许字符串的内容在多行出现,新的行被转换为n
,其他所有的空白字符都被完整的按照文本原样保留,本质也是一个 GString 实例。Groovy 中闭包是最有用的特性之一,也是非常重要的特性,可以说 Groovy 精髓都围绕闭包。
在一个方法调用的后面或者变量等号右侧,放置闭包代码在一对花括号里,闭包的参数和代码通过箭头->
进行分隔,一个参数的可以省略箭头,可以使用默认的it
隐式参数。特别注意,花括号显示了闭包声明的时间,不是执行的时间。
简单声明方式的闭包
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 】def testStr = ''
(1..10).each({testStr += it
})
assert testStr == '12345678910' //true
赋值方式声明闭包
def Closure getPrintCustomer() {return { line -> println line }
}def printCustomer = { line -> println line }
可以看到,无论如上哪种声明闭包方式,花括号都是其中的主角,而在 Groovy 中的花括号作用一般都是用来标明构建了一个新的闭包对象或者一个 Groovy 代码块。
同理,上面两种声明方式也能发现,闭包就是一个 Closure 对象实例,他们可以通过变量进行引用,能够作为参数传递,可以在闭包中调用方法,也可以作为一个方法的返回实例出现。
重用已有的方法声明闭包
一般我们的方法会有一个方法体,可选的返回值,能接受参数,并且能被调用。而通过上面两种闭包的声明和使用能发现,闭包的结构和特性和普通方法也很相似,因此 Groovy 可以让我们作为一个闭包重用已经在方法中存在的代码。
引用一个方法作为闭包是使用reference.&操作符
,reference
是闭包调用时使用的对象实例,就像普通方法调用 reference.someMethod()
一样。.&
可以被称为方法闭包操作符。
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 】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) == 7assert test(7) == 12
assert test.call(7) == 12
上面我们见证了闭包的声明和调用,为了循序渐进,我们这里先看看上面的代码的 class 字节码到底是啥样的,因为前面我们说过,Groovy 本质就是 JVM 语言,其编译后也是 class,只是 classPath 比 JDK 多了 GDK 而已。
//vy
def testStr = ''
(1..10).each({testStr += it
})
print(testStr)
如上代码使用vy
编译产物为test.class
和test$_run_closure1.class
,_run_closure1 为 test 的内部类名,我们使用 IDEA 拖入这两个产物 class 文件查看反编译如下(这是反编译产物,实际可能有出入):
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 】import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.Reference;
import groovy.lang.Script;
ansform.Generated;
vy.runtime.GeneratedClosure;
vy.runtime.InvokerHelper;
vy.runtime.ScriptBytecodeAdapter;
vy.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 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].(), it);testStr.set(var10000);return var10000;}public Object getTestStr() {CallSite[] var1 = $getCallSiteArray();();}@Generatedpublic Object doCall() {CallSite[] var1 = $getCallSiteArray();return this.doCall((Object)null);}}//range的翻译var1[1].ateRange(1, 10, (boolean)1), new _run_closure1(this, this));return var1[2].callCurrent(this, ());}
}
上面代码不用看懂所有,也没有必要,你只要领悟精髓即可,即 Groovy 的一切都是对象,闭包的本质就是 Closure 实例对象即可,其他都可以理解成是 Java 的一种拓展,你甚至可以理解成他就是 Java 的一个三方框架,只是这个框架不仅仅提供了代码编写的规范,还有自己的一套构建规则,这才是本质思想。
渐渐的我们慢慢揭开了闭包的面纱,Groovy 的闭包有委托的概念,即在闭包中修改委托对象和委托策略的能力,能够让它在 DSL(Domain Specific Languages)领域(譬如 Gradle 等)得到升华。理解闭包的委托功能,就需要先弄明白groovy.lang.Closure
类的三个属性,即 this、owner 和 delegate。
this: 指向定义闭包所在的封闭类。在闭包中可以通过getThisObject()
方法获取定义闭包的封闭类,和显式调用 this 是等价的效果。这个不用再举例子了,比较简单,可以看下一小节的案例。
owner: 对应定义声明闭包的封闭对象,可以是类或者闭包。它与 this 有点类似,最大的不同是 owner 指向的是直接包裹它的对象(普通对象或者闭包对象)。这个不用再举例子了,比较简单,可以看下一小节的案例。
delegate: 可以对应任何对象。this 和 owner 都属于闭包的标准语法范围,而 delegate 指向的是闭包所使用的用户自定义的对象。默认情况下 delegate 被设置成 owner。我们可以通过 delegate 关键字和getDelegate()
、setDelegate(x)
方法来使用委托对象。样例如下:
//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 】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 str1def 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 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 】class Person {String namedef pretty = { "My name is $name" }String toString() {pretty()}
}
class Thing {String name
}def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')String() == 'My name is Sarah'
p.pretty.delegate = t
/*** p 的 owner 上有 name 属性,默认策略 Closure.OWNER_FIRST,* 所以还是走的 p 的属性值。*/
String() == 'My name is Sarah'
/*** p 的 owner 上有 name 属性,策略是 Closure.DELEGATE_FIRST,* 所以和上面策略刚好相反,delegate 首先被委托,其次再委托给 owner,* 所以走的是 t 的属性值。*/
solveStrategy = Closure.DELEGATE_FIRST
String() == 'My name is Teapot'
class Person {String nameint agedef 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 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 】class Mather {int field = 1int foo() {return 2}/*** 指明这个方法将返回一个闭包对象* 注意:在声明期间闭包的列表将被返回,闭包仍然没有被调用*/Closure birth(param) {def local = 3def 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 闭包委托代理的精髓就介绍完毕了,下面就是该揭开闭包本质的时候了。
Groovy 闭包的本质是一个 Closure 实例对象,GDK 里的 groovy.lang.Closure
是一个普通的 java 类,其内部有很多特性,具体感兴趣的可以去看下源码,如下是对源码部分删减的简介:
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;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) CachedClass(getClass());parameterTypes = ParameterTypes();maximumNumberOfParameters = MaximumNumberOfParameters();}....../*** 熟悉的委托策略设置*/public void setResolveStrategy(int resolveStrategy) {solveStrategy = 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:Property(this.delegate, property);case OWNER_ONLY:Property(this.owner, property);case TO_SELF:Property(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 Property(firstTry, property);} catch (MissingPropertyException e1) {if (secondTry != null && firstTry != this && firstTry != secondTry) {try {// let's try getting the property on the second Property(secondTry, property);} catch (GroovyRuntimeException e2) {// ignore, we'll throw e1}}throw e1;} catch (MissingFieldException e2) { // see GROOVY-5875if (secondTry != null && firstTry != this && firstTry != secondTry) {try {// let's try getting the property on the second Property(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 objectInvokerHelper.setProperty(firstTry, property, newValue);} catch (GroovyRuntimeException e1) {if (firstTry != null && firstTry != this && firstTry != secondTry) {try {// let's try setting the property on the second objectInvokerHelper.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 args) {try {return (V) getMetaClass().invokeMethod(this,"doCall",args);} catch (InvokerInvocationException e) {ExceptionUtils.Cause());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;}/*** 依赖闭包是接受一个参数还是多个参数,通过闭包的这个方法能获取到期望的参数数量信息(类型,如果声明了类型)。* 譬如调用闭包的前面先 ParameterTypes().size() 获取参数个数再决定怎么调用等。*/public Class[] getParameterTypes() {return parameterTypes;}/*** 返回一个当前闭包的克隆版,这个克隆版有一个附加的 writeTo(Writer) 方法来直接将闭包的结果输出到给定的 Writer 中。*/public Closure asWritable() {return new WritableClosure();}/*** 执行闭包代码,本质是调用了闭包的 call 方法*/public void run() {call();}/*** Closure 的 curry 方法返回当前闭包的一个克隆品,这个克隆品已经绑定 了一个或者多个给定的参数,参数的绑定是从左向右进行的。* def sum = {x, y => return x + y }* def sumOne = sum.curry(1)* assert sumOne(5) == 6* Curry 最强大的地方是当闭包的参数是闭包本身的时候,通常在函数式编程时使用到。*/public Closure<V> curry( arguments) {return new CurriedClosure<V>(this, arguments);}......
}
到此算是把 Groovy 的闭包扒了一层皮,完全摸透了,从表象到内部机制,对你日常使用应该是没问题了。你要是高端玩家则 Closure 类里面还有很多有趣的方法等你使用,不过你都搞懂了本质原理,那些方法基本就是纯 API 用法了,这里不再展开。
【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 】
本文发布于:2024-02-04 16:04:04,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170711211556975.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |