[英]Groovy 2.4 variable scope in closure with @Field annotation
Can someone explain to me why in closure2 initVars('c')
is not able to modify the referenced object if @Field
is used in declaration? 有人可以向我解释为什么在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
}
Output: 输出:
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
Output without @Field
and def
, with just lines4 = "a"
in script scope. 没有@Field
和def
输出,在脚本范围内只有lines4 = "a"
。 This appears normal to me. 这对我来说似乎很正常。
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
I saw same behavior in groovy2.5-beta and groovy 2.6-alpha. 我在groovy2.5-beta和groovy 2.6-alpha中看到了相同的行为。
Using @Field
annotation on a script variable changes a scope of this variable from a local one to a Script
class one: 在脚本变量上使用@Field
注释会将此变量的范围从本地变量更改为Script
类1:
Variable annotation used for changing the scope of a variable within a script from being within the run method of the script to being at the class level for the script. 用于将脚本中的变量范围从脚本的run方法更改为脚本的类级别的变量注释。
The annotated variable will become a private field of the script class. 带注释的变量将成为脚本类的私有字段。 The type of the field will be the same as the type of the variable. 字段的类型将与变量的类型相同。 Example usage: 用法示例:
import groovy.transform.Field @Field List awe = [1, 2, 3] def awesum() { awe.sum() } assert awesum() == 6
In this example, without the annotation, variable awe would be a local script variable (technically speaking it will be a local variable within the run method of the script class). 在这个例子中,没有注释,变量awe将是一个本地脚本变量(从技术上讲,它将是脚本类的run方法中的局部变量)。 Such a local variable would not be visible inside the awesum method. 在awesum方法中不会看到这样的局部变量。 With the annotation, awe becomes a private List field in the script class and is visible within the awesum method. 使用注释,awe成为脚本类中的私有List字段,并在awesum方法中可见。
Source: http://docs.groovy-lang.org/2.4.12/html/gapi/groovy/transform/Field.html 资料来源: http : //docs.groovy-lang.org/2.4.12/html/gapi/groovy/transform/Field.html
Every Groovy script extends groovy.lang.Script
class and the body of the script is executed inside Script.run()
method. 每个Groovy脚本都扩展了groovy.lang.Script
类,脚本的主体在Script.run()
方法中执行。 Groovy passes variables to this script using Binding
object. Groovy使用Binding
对象将变量传递给此脚本。 When you change a scope of a local script variable to a class level then there is no binding for this variable passed to a closure, because binding
object contains only local-scoped variables . 当您将本地脚本变量的范围更改为类级别时,传递给闭包的此变量没有绑定,因为binding
对象仅包含本地范围的变量 。 Compare these two screenshots I made. 比较我制作的这两个截图。 First one shows what the binding
object looks like when we call initVars(String pref)
for the first time and lines4
is a local script variable: 第一个显示第一次调用initVars(String pref)
时binding
对象的样子,而lines4
是本地脚本变量:
And here is same breakpoint but now lines4
is a @Field def lines4
variable: 这里是相同的断点,但现在lines4
是@Field def lines4
变量:
As you can see there is no binding for lines4
variable in binding
object, but there is a class field called lines4
, while this binding is present in the first screenshot attached. 正如您所看到的, binding
对象中的lines4
变量没有绑定,但是有一个名为lines4
的类字段,而此绑定存在于附加的第一个屏幕截图中。
When you call 你打电话的时候
lines4 += 'p1'
in the first closure, local binding for lines4
is created and it is initialized with a current value of a this.lines4
value. 在第一个闭包中,创建了lines4
局部绑定,并使用this.lines4
值的当前值初始化它。 It happens because Script.getProperty(String property)
is implemented in following way: 这是因为Script.getProperty(String property)
以下列方式实现:
public Object getProperty(String property) {
try {
return binding.getVariable(property);
} catch (MissingPropertyException e) {
return super.getProperty(property);
}
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Script.java#L54 资料来源: https : //github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/Script.java#L54
So it firstly checks if there is a binding for a variable you access in the closure and when it does not exist it passes execution to a parent's getProperty(name)
implementation - in our case it just returns class property value. 因此,它首先检查是否存在对您在闭包中访问的变量的绑定以及何时不存在它将执行传递给父的getProperty(name)
实现 - 在我们的示例中它只返回类属性值。 At this point this.lines4
is equal to b
and this is the value that is returned. 此时this.lines4
等于b
,这是返回的值。
initVars(String pref)
method accesses class field, so when you call it it always overrides Script.lines4
property. initVars(String pref)
方法访问类字段,因此当您调用它时,它始终会覆盖Script.lines4
属性。 But when you call 但是当你打电话时
lines4 += 'q1'
in the second closure, the binding lines4
for a closure already exists and its value is bp1
- this value was associated in the first closure call. 在第二个闭包中,闭包的绑定lines4
已经存在,其值为bp1
- 该值在第一个闭包调用中关联。 That's why you don't see c
after calling initVars('c')
. 这就是为什么你在调用initVars('c')
后没有看到c
的原因。 I hope it helps. 我希望它有所帮助。
binding
works in a script explained 更新:脚本中的binding
如何解释 Let's get a little deeper to get better understanding what is going on under the hood. 让我们更深入一点,以便更好地了解幕后发生的事情。 This is what your Groovy script looks like when it is compiled to a bytecode: 这是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();
}
Two things worth mentioning at this moment: 此时值得一提的两件事:
@Field def lines4
is compiled to a class field java.lang.Object lines4;
@Field def lines4
被编译成类字段java.lang.Object lines4;
void initVars(String pref)
method is compiled to public void initVars(java.lang.String);
void initVars(String pref)
方法被编译为public void initVars(java.lang.String);
class method. 类方法。 For a simplicity you can assume that the rest content (excluding lines4
and initVars
method) of your script is inlined to public java.lang.Objectrun()
method. 为简单起见,您可以假设脚本的其余内容(不包括lines4
和initVars
方法)内联到public java.lang.Objectrun()
方法。
initVars
always accesses class field lines4
because it has direct access to this field. initVars
始终访问类字段lines4
因为它可以直接访问此字段。 Decompiling this method to a bytecode shows us this: 将此方法反编译为字节码向我们显示:
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
Operation 56 is a opcode for a assigning value to a field. 操作56是用于为字段分配值的操作码。
Now let's understand what happens when both closures gets called. 现在让我们了解当两个闭包被调用时会发生什么。 First thing worth mentioning - both closures have delegate
field set to the script object that is being executed. 首先要提到的是 - 两个闭包都delegate
字段设置为正在执行的脚本对象。 We know that it extends groovy.lang.Script
class - a class that uses binding
private field to store all bindings (variables) available in the script runtime. 我们知道它扩展了groovy.lang.Script
类 - 一个使用binding
私有字段来存储脚本运行时中可用的所有绑定(变量)的类。 This is important observation, because groovy.lang.Script
class overrides: 这是重要的观察,因为groovy.lang.Script
类重写:
public Object getProperty(String property)
public void setProperty(String property, Object newValue)
Both methods use binding
to lookup and store variables used in the script runtime. 两种方法都使用binding
来查找和存储脚本运行时中使用的变量。 getProperty
gets called any time you read local script variable and setProperty
gets called any time you assign a value to script local variable. getProperty
读取本地脚本变量时都会调用getProperty
并且setProperty
为脚本局部变量赋值时都会调用setProperty
。 That's why code like: 这就是为什么代码如下:
lines4 += 'p1'
generates sequence like: 生成如下序列:
getProperty -> value + 'p1' -> setProperty
In your example first attempt of reading lines4
ends up with returning a value from parent class (it happens if binding is not found, then GroovyObjectSupport.getProperty(name)
is called and this one returns a value of a class property with given name). 在您的示例中,第一次尝试读取lines4
最终会从父类返回一个值(如果未找到绑定,则会发生这种情况,然后GroovyObjectSupport.getProperty(name)
,并返回具有给定名称的类属性的值)。 When closure assigns a value to a lines4
variable then a binding is created. 当closure为lines4
变量赋值时,则创建绑定。 And because both closures share same binding
object (they use delegate to the same instance), when second closure reads or writes line4
variable then it uses previously created binding. 并且因为两个闭包共享相同的binding
对象(它们使用委托给同一个实例),当第二个闭包读取或写入line4
变量时,它使用先前创建的绑定。 And initVars
does not modify binding because as I shown you earlier it accesses class field directly. 并且initVars
不会修改绑定,因为正如我之前所示,它直接访问类字段。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.