【发布时间】:2017-03-18 11:38:06
【问题描述】:
Java lambda 不能修改周围范围内的变量(不会关闭)。但是 Groovy 的闭包怎么会呢?它的内部实现是什么?
例如,这里的闭包怎么会增加外部变量i?每次迭代都会创建一个内部对象吗?
for (int i = 0; i < n;) {
{ -> i++ }.run()
}
【问题讨论】:
标签: java groovy lambda closures internal
Java lambda 不能修改周围范围内的变量(不会关闭)。但是 Groovy 的闭包怎么会呢?它的内部实现是什么?
例如,这里的闭包怎么会增加外部变量i?每次迭代都会创建一个内部对象吗?
for (int i = 0; i < n;) {
{ -> i++ }.run()
}
【问题讨论】:
标签: java groovy lambda closures internal
在这种情况下,i 被装箱在一个可变的 groovy 引用中。在 Java 中,设置 i 将更改此循环所在方法的局部变量。这带来了一堆技术问题,如果函数对象离开方法会发生什么。但在 groovy 中,i 驻留在堆上。
这个解释来自我理解的字节码,你可以通过命令得到:
javap -c <name of closure class>
查看方法doCall,首先查找函子的CallSite对象(因为lambda可以共享它们的类,调用站点基本上是捕获的局部变量的集合),并检索到具体的i :
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;
之后,将结果加载回 groovy 引用中:
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,它驻留在堆上,而不是在局部变量槽中。
【讨论】:
println i.class,我会看到它在行动吗?另外,Groovy 什么时候知道它必须包装它?编译还是运行时? @CompileStatic 在这方面呢?
ints 包装在一个groovy 引用中,而@CompileStatic 并没有改变这一点。 println i.class 将显示 java.lang.Integer,groovy 引用似乎是隐藏的实现细节。