Artem Novikov
Artem Novikov

Reputation: 4644

How groovy closures work internally?

A Java lambda can't modify variables in the surrounding scope (doesn't closes over). But how come Groovy closures can? What's its internal implementation?

For example, here, how come the closure can increment the outside variable i? Will an internal object be created for each iteration?

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

Upvotes: 0

Views: 329

Answers (1)

Jorn Vernee
Jorn Vernee

Reputation: 33865

In this case, i is boxed inside a mutable groovy reference. In Java, setting i would be changing a local variable of the method in which this loop resides. Which brings up a bunch of technical questions about what would happen if the function object leaves the method. But in groovy, i resides on the heap.

This explanation comes from the bytecode as I understand it, which you can get with the command:

javap -c <name of closure class>

Looking at the method doCall, first the CallSite object for the functor is looked up (since lambdas can share their classes, call sites are basically collections of captured local variables), and the specific i is retreived:

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

As you can see, the type of i is groovy.lang.Reference. Next, the number is incremented:

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

After that, the result is loaded back into the groovy reference at:

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

It would be similar to doing something like this in Java:

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

Where I used AtomicInteger to represent a mutable int that resides on the heap, rather than in a local variable slot.

Upvotes: 3

Related Questions