2017-03-18 108 views
0

Java lambda無法修改周圍作用域中的變量(不會關閉)。但是Groovy如何關閉?它的內部實現是什麼?Groovy封閉如何在內部工作?

例如,在這裏,如何關閉可以增加外部變量i?是否會爲每次迭代創建一個內部對象?

for (int i = 0; i < n;) { 
    { -> i++ }.run() 
} 

回答

2

在這種情況下,i是盒裝在一個可變的groovy參考。在Java中,設置i將更改此循環所在方法的局部變量。這引發了一堆關於如果函數對象離開方法會發生什麼的技術問題。但在常規中,i駐留在堆上。

這解釋來自我所理解的字節碼,您可以用命令得到:

javap -c <name of closure class> 

望着方法doCall,首先CallSite對象的仿函數擡頭(因爲lambda表達式可以分享他們的班,調用點基本上都是捕獲局部變量的集合),具體i是retreived:

10: getfield  #29     // Field i:Lgroovy/lang/Reference; 

正如你所看到的,i類型是groovy.lang.Reference。接下來,數量遞增:

27: invokestatic #51     // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.next:(Ljava/lang/Number;)Ljava/lang/Number; 

之後,結果被裝載回常規參考在:

42: invokevirtual #61     // Method groovy/lang/Reference.set:(Ljava/lang/Object;)V 

這將是類似於做這樣的事情在Java中:

for (AtomicInteger i = new AtomicInteger(); i.get() < n;) { 
    ((Runnable)() -> System.out.println(i.getAndIncrement())).run(); 
} 

我在哪裏使用AtomicInteger來表示一個可變的int,它駐留在堆上,而不是局部變量槽中。

+0

謝謝。你有證據嗎?如果我做'println i.class',我會看到這個動作嗎?此外,Groovy何時知道它必須包裝它?編譯還是運行時?在這方面呢'@ComileStatic'呢? –

+0

@ArtemNovikov證明是在仿函數的字節碼中。起初我沒有包括它,因爲我無法將Groovy鍋爐板與實際實施分離。我已盡最大努力。我不熟悉groovy,我只是碰巧知道如何讀字節碼。也許你可以自己測試一些東西。從我自己的測試看來,groovy似乎總是將'int'包裝在一個常規的引用中,並且'@ CompileStatic'不會改變它。 'println i.class'將顯示'java.lang.Integer',groovy引用似乎是一個隱藏的實現細節。 –