有人可以向我解释为什么在声明中使用closure2时initVars('c')无法修改引用的对象@Field?
import groovy.transform.Field;
@Field def lines4 = "a";
void initVars(String pref){
println('init:'+lines4+' '+pref) //*3.init:a b *7.init:b c
lines4 = pref;
}
println("closure1") ///1. closure1
1.times {
println(lines4) ///2. a
initVars('b') ///3. init:a b
lines4 += 'p1'
println(lines4) ///4. bp1
}
println("closure2") ///5. closure2
1.times {
println(lines4) ///6. bp1
initVars('c') ///7. init:b c
println(lines4) ///8. bp1 Why not c
lines4 += 'q1'
println(lines4) ///9. bp1q1 Why not cq1
}
Run Code Online (Sandbox Code Playgroud)
输出:
C:\projects\ATT>groovy test.groovy
1. closure1
2. a
3. init:a b
4. bp1
5. closure2
6. bp1
7. init:b c
8. bp1
9. bp1q1
Run Code Online (Sandbox Code Playgroud)
没有@Field和的输出def,仅lines4 = "a"在脚本范围内.这对我来说似乎很正常.
C:\projects\ATT>groovy test.groovy
1. closure1
2. a
3. init:a
4. bp1
5. closure2
6. bp1
7. init:bp1
8. c
9. cq1
Run Code Online (Sandbox Code Playgroud)
我在groovy2.5-beta和groovy 2.6-alpha中看到了相同的行为.
@Field在脚本变量上使用注释会将此变量的范围从本地变量更改为第一Script类:
用于将脚本中的变量范围从脚本的run方法更改为脚本的类级别的变量注释.
带注释的变量将成为脚本类的私有字段.字段的类型将与变量的类型相同.用法示例:
Run Code Online (Sandbox Code Playgroud)import groovy.transform.Field @Field List awe = [1, 2, 3] def awesum() { awe.sum() } assert awesum() == 6在这个例子中,没有注释,变量awe将是一个本地脚本变量(从技术上讲,它将是脚本类的run方法中的局部变量).在awesum方法中不会看到这样的局部变量.使用注释,awe成为脚本类中的私有List字段,并在awesum方法中可见.
资料来源:http://docs.groovy-lang.org/2.4.12/html/gapi/groovy/transform/Field.html
每个Groovy脚本都扩展了groovy.lang.Script类,脚本的主体在Script.run()方法内执行.Groovy使用Bindingobject 将变量传递给此脚本.当您将本地脚本变量的范围更改为类级别时,传递给闭包的此变量没有绑定,因为binding对象仅包含本地范围的变量.比较我制作的这两个截图.第一个显示binding当我们initVars(String pref)第一次调用时对象的样子并且lines4是本地脚本变量:
这里是相同的断点,但现在lines4是一个@Field def lines4变量:
正如您所看到的lines4,binding对象中的变量没有绑定,但是有一个类字段被调用lines4,而此绑定存在于附加的第一个屏幕截图中.
你打电话的时候
lines4 += 'p1'
Run Code Online (Sandbox Code Playgroud)
在第一个闭包中,lines4创建了本地绑定,并使用值的当前值初始化它this.lines4.它发生的原因Script.getProperty(String property)是以下列方式实现:
public Object getProperty(String property) {
try {
return binding.getVariable(property);
} catch (MissingPropertyException e) {
return super.getProperty(property);
}
}
Run Code Online (Sandbox Code Playgroud)
资料来源:https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Script.java#L54
因此,它首先检查是否存在对您在闭包中访问的变量的绑定以及何时不存在它将执行传递给父的getProperty(name)实现 - 在我们的示例中它只返回类属性值.此时this.lines4等于b,这是返回的值.
initVars(String pref)方法访问类字段,所以当你调用它时,它总是覆盖Script.lines4属性.但是当你打电话时
lines4 += 'q1'
Run Code Online (Sandbox Code Playgroud)
在第二个闭包中,闭包的绑定lines4已经存在,其值为bp1- 该值在第一个闭包调用中关联.这就是你c打电话后没看到的原因initVars('c').我希望它有所帮助.
binding脚本中的工作原理如何解释让我们更深入一点,以便更好地了解幕后发生的事情.这是Groovy脚本在编译为字节码时的样子:
Compiled from "script_with_closures.groovy"
public class script_with_closures extends groovy.lang.Script {
java.lang.Object lines4;
public static transient boolean __$stMC;
public script_with_closures();
public script_with_closures(groovy.lang.Binding);
public static void main(java.lang.String...);
public java.lang.Object run();
public void initVars(java.lang.String);
protected groovy.lang.MetaClass $getStaticMetaClass();
}
Run Code Online (Sandbox Code Playgroud)
此时值得一提的两件事:
@Field def lines4 被编译为类字段 java.lang.Object lines4;void initVars(String pref)方法被编译为public void initVars(java.lang.String);类方法.为简单起见,您可以假设脚本的其余内容(排除lines4和initVars方法)内联到public java.lang.Objectrun()方法中.
initVars始终访问类字段,lines4因为它可以直接访问此字段.将此方法反编译为字节码向我们显示:
public void initVars(java.lang.String);
Code:
0: invokestatic #19 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
3: astore_2
4: aload_2
5: ldc #77 // int 5
7: aaload
8: aload_0
9: aload_2
10: ldc #78 // int 6
12: aaload
13: aload_2
14: ldc #79 // int 7
16: aaload
17: aload_2
18: ldc #80 // int 8
20: aaload
21: ldc #82 // String init:
23: aload_0
24: getfield #23 // Field lines4:Ljava/lang/Object;
27: invokeinterface #67, 3 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
32: ldc #84 // String
34: invokeinterface #67, 3 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
39: aload_1
40: invokeinterface #67, 3 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.call:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
45: invokeinterface #52, 3 // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.callCurrent:(Lgroovy/lang/GroovyObject;Ljava/lang/Object;)Ljava/lang/Object;
50: pop
51: aload_1
52: astore_3
53: aload_3
54: aload_0
55: swap
56: putfield #23 // Field lines4:Ljava/lang/Object;
59: aload_3
60: pop
61: return
Run Code Online (Sandbox Code Playgroud)
操作56是用于为字段分配值的操作码.
现在让我们了解当两个闭包被调用时会发生什么.首先要提到的是 - 两个闭包都将delegate字段设置为正在执行的脚本对象.我们知道它扩展了groovy.lang.Scriptclass - 一个使用binding私有字段来存储脚本运行时中可用的所有绑定(变量)的类.这是重要的观察,因为groovy.lang.Script类覆盖:
public Object getProperty(String property)public void setProperty(String property, Object newValue)这两种方法都binding用于查找和存储脚本运行时中使用的变量.getProperty每次读取本地脚本变量时setProperty都会调用它,并在任何时候为脚本局部变量赋值时调用它.这就是为什么代码如下:
lines4 += 'p1'
Run Code Online (Sandbox Code Playgroud)
生成如下序列:
getProperty -> value + 'p1' -> setProperty
Run Code Online (Sandbox Code Playgroud)
在您的示例中,第一次读取操作lines4最终会从父类返回一个值(如果未找到绑定,则会发生这种情况,然后GroovyObjectSupport.getProperty(name)调用它,然后返回一个具有给定名称的类属性的值).当closure为lines4变量赋值时,则创建绑定.并且因为两个闭包共享相同的binding对象(它们使用委托给同一个实例),当第二个闭包读取或写入line4变量时,它使用先前创建的绑定.并且initVars不会修改绑定,因为正如我之前所示,它直接访问类字段.
| 归档时间: |
|
| 查看次数: |
2466 次 |
| 最近记录: |