【问题标题】:How groovy closures work internally?groovy 闭包如何在内部工作?
【发布时间】:2017-03-18 11:38:06
【问题描述】:

Java lambda 不能修改周围范围内的变量(不会关闭)。但是 Groovy 的闭包怎么会呢?它的内部实现是什么?

例如,这里的闭包怎么会增加外部变量i?每次迭代都会创建一个内部对象吗?

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

【问题讨论】:

    标签: java groovy lambda closures internal


    【解决方案1】:

    在这种情况下,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 在这方面呢?
    • @ArtemNovikov 证明在函子的字节码中。起初我没有包含它,因为我在将 Groovy 样板与实际实现分开时遇到了麻烦。我已经尽了最大的努力。我不太熟悉 groovy,我只是碰巧知道如何读取字节码。也许你可以自己测试一些东西。从我自己的测试来看,groovy 似乎总是将ints 包装在一个groovy 引用中,而@CompileStatic 并没有改变这一点。 println i.class 将显示 java.lang.Integer,groovy 引用似乎是隐藏的实现细节。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-15
    • 2016-07-04
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    • 2010-10-23
    相关资源
    最近更新 更多