2017-03-18 103 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引用似乎是一个隐藏的实现细节。 –