簡體   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