gpt4 book ai didi

groovy - 带有@Field批注的Groovy 2.4变量范围

转载 作者:行者123 更新时间:2023-12-01 09:15:07 26 4
gpt4 key购买 nike

有人可以向我解释为什么在声明中使用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

输出不带 @Fielddef,而在脚本作用域中只有 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对象 仅包含本地范围的变量。比较我制作的这两个屏幕截图。第一个显示了当我们第一次调用 bindinginitVars(String pref)对象的外观,而 lines4是本地脚本变量:

enter image description here

这里是相同的断点,但现在 lines4@Field def lines4变量:

enter image description here

如您所见, 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);类方法。

  • 为简单起见,您可以假定脚本的其余内容(不包括 lines4initVars方法)都内联到 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/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com