有人可以向我解释为什么在声明中使用initVars('c')
时,在closure2中@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
}
输出:
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
输出不带
@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
我在groovy2.5-beta和groovy 2.6-alpha中看到了相同的行为。
最佳答案
在脚本变量上使用@Field
批注会将此变量的范围从本地变量更改为Script
类1:
变量注释,用于将脚本内变量的范围从脚本的run方法内更改为脚本的类级别。
带注释的变量将成为脚本类的私有字段。字段的类型将与变量的类型相同。用法示例:
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使用 Binding
对象将变量传递给此脚本。当您将本地脚本变量的范围更改为类级别时,此变量没有绑定传递给闭包,因为binding
对象仅包含本地范围的变量。比较我制作的这两个屏幕截图。第一个显示了当我们第一次调用binding
时initVars(String pref)
对象的外观,而lines4
是本地脚本变量:这里是相同的断点,但现在
lines4
是@Field def lines4
变量:如您所见,
lines4
对象中没有binding
变量的绑定,但是有一个名为lines4
的类字段,而该绑定在所附的第一个屏幕快照中。您打电话的时候
lines4 += 'p1'
在第一个闭包中,创建
lines4
的本地绑定,并使用this.lines4
值的当前值初始化它。这是因为Script.getProperty(String property)
是通过以下方式实现的:public Object getProperty(String property) {
try {
return binding.getVariable(property);
} catch (MissingPropertyException e) {
return super.getProperty(property);
}
}
资料来源: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'
在第二个闭包中,一个闭包的绑定
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();
}
目前有两件事值得一提:
@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
操作56是用于将值分配给字段的操作码。
现在让我们了解一下两个闭包都被调用时会发生什么。值得一提的第一件事-两个闭包都将
delegate
字段设置为正在执行的脚本对象。我们知道它扩展了groovy.lang.Script
类-使用 binding
private field来存储脚本运行时中可用的所有绑定(变量)的类。这很重要,因为groovy.lang.Script
类覆盖:public Object getProperty(String property)
public void setProperty(String property, Object newValue)
两种方法都使用
binding
查找和存储脚本运行时中使用的变量。只要您读取本地脚本变量,就可以调用getProperty
,而只要您为脚本本地变量分配值,就可以调用setProperty
。这就是为什么代码如下:lines4 += 'p1'
生成如下序列:
getProperty -> value + 'p1' -> setProperty
在您的示例中,首次尝试读取
lines4
最终会从父类返回一个值(如果未找到绑定,则会发生这种情况,然后 GroovyObjectSupport.getProperty(name)
is called会返回给定名称的类属性的值)。当闭包为lines4
变量分配值时,将创建绑定。并且由于两个闭包共享相同的binding
对象(它们使用委托给同一实例),因此当第二个闭包读取或写入line4
变量时,它将使用先前创建的绑定。 initVars
不会修改绑定,因为正如我之前所展示的,它直接访问类字段。
关于groovy - 带有@Field批注的Groovy 2.4变量范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46579944/