简体   繁体   中英

Recursion when calling Owner in Groovy Closure

I have been trying to understand Groovy scripts but an example I made is hitting an unexpected StackOverflow.

The foo closure prints out its owner as 'Tank', before defining another closure named bar. I expected bar to print the owner as something like Tank$foo.

class Tank {
    def foo = {
        println "Owner is $owner"

        def bar = {
            println "      Owner is $owner"
        }
        bar()
    }

    static void main(String ...args){
        def t = new Tank() ;
        t.foo()
    }
}

Well... The short answer is you should change your bar method to the following:

def bar = {
    println "      Owner is ${owner.toString()}"
}

That will prevent the foo closure referenced by the $owner from evaluation. Because it will no longer be a closure but rather a string representation of it. This is what you want to achieve in your example.

The full answer is bit too complicated and has its history...

String interpolation in Groovy is implemented using the GString class. When the compiler comes across your " Owner is $owner" it first breaks that value into different pieces where the " Owner is " is kept as a string whereas $owner goes to the so-called values. This is how an instance of GString gets constructed , it's a combination of those two pieces.

If we then look into the Groovy source code, we can see how the GString.toString() is evaluated. The existing implementation iterates over the both arrays, strings and values , and if the value is a closure then it just eagerly evaluates (calls) that. This is why you fall into the infinite recursion in your example. Here is the snippet of GString.toString() found in Groovy source code (spot the c.call(...) ):

if (value instanceof Closure) {
    final Closure c = (Closure) value;

    if (c.getMaximumNumberOfParameters() == 0) {
        InvokerHelper.write(out, c.call());
    } else if (c.getMaximumNumberOfParameters() == 1) {
        c.call(out);
    } else {
        throw new GroovyRuntimeException("Trying to evaluate a GString containing a Closure taking "
                + c.getMaximumNumberOfParameters() + " parameters");
    }
} else {
    InvokerHelper.write(out, value);
}

Interestingly, I also found when this was first introduced and the discussion about potentially changing this behaviour. The discussion issue is still open.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM