groovy 闭包在内部是如何工作的?
How groovy closures work internally?
A Java lambda 无法修改周围范围内的变量(不会关闭)。但是Groovy闭包怎么会呢?它的内部实现是什么?
比如这里,闭包怎么可以自增外部变量i
?每次迭代都会创建一个内部对象吗?
for (int i = 0; i < n;) {
{ -> i++ }.run()
}
在这种情况下,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
。
A Java lambda 无法修改周围范围内的变量(不会关闭)。但是Groovy闭包怎么会呢?它的内部实现是什么?
比如这里,闭包怎么可以自增外部变量i
?每次迭代都会创建一个内部对象吗?
for (int i = 0; i < n;) {
{ -> i++ }.run()
}
在这种情况下,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
。