繁体   English   中英

使用@Field注释关闭Groovy 2.4变量范围

[英]Groovy 2.4 variable scope in closure with @Field annotation

有人可以向我解释为什么在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
}

输出:

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对象仅包含本地范围的变量 比较我制作的这两个截图。 第一个显示第一次调用initVars(String pref)binding对象的样子,而lines4是本地脚本变量:

在此输入图像描述

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

在此输入图像描述

正如您所看到的, binding对象中的lines4变量没有绑定,但是有一个名为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 - 该值在第一个闭包调用中关联。 这就是为什么你在调用initVars('c')后没有看到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();
}

此时值得一提的两件事:

  1. @Field def lines4被编译成类字段java.lang.Object lines4;
  2. 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私有字段来存储脚本运行时中可用的所有绑定(变量)的类。 这是重要的观察,因为groovy.lang.Script类重写:

两种方法都使用binding来查找和存储脚本运行时中使用的变量。 getProperty读取本地脚本变量时都会调用getProperty并且setProperty为脚本局部变量赋值时都会调用setProperty 这就是为什么代码如下:

lines4 += 'p1'

生成如下序列:

getProperty -> value + 'p1' -> setProperty

在您的示例中,第一次尝试读取lines4最终会从父类返回一个值(如果未找到绑定,则会发生这种情况,然后GroovyObjectSupport.getProperty(name)并返回具有给定名称的类属性的值)。 当closure为lines4变量赋值时,则创建绑定。 并且因为两个闭包共享相同的binding对象(它们使用委托给同一个实例),当第二个闭包读取或写入line4变量时,它使用先前创建的绑定。 并且initVars不会修改绑定,因为正如我之前所示,它直接访问类字段。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM