I'm playing with closures and seeing this odd behavior that I can't quite explain:
groovy:000> ({ println owner })()
groovysh_evaluate@200b6145
===> null
groovy:000> ({ println "${owner}" })()
groovysh_evaluate@2bf75a70
===> null
groovy:000> ({ ({ println owner })() })()
groovysh_evaluate$_run_closure1@10f67a01
===> null
groovy:000> ({ ({ println "${owner}" })() })()
ERROR java.lang.StackOverflowError:
null
at groovysh_evaluate$_run_closure1_closure2.doCall (groovysh_evaluate:2)
at groovysh_evaluate$_run_closure1_closure2.doCall (groovysh_evaluate)
at groovysh_evaluate$_run_closure1.doCall (groovysh_evaluate:2)
at groovysh_evaluate$_run_closure1_closure2.doCall (groovysh_evaluate:2)
at groovysh_evaluate$_run_closure1_closure2.doCall (groovysh_evaluate)
at groovysh_evaluate$_run_closure1.doCall (groovysh_evaluate:2)
<stacktrace repeats>
I figure it has something to do with the fact that the ${}
is itself a closure, but I can't quite pin down why this happens. The issue does appear related to accessing owner
as I've not seen it happen with other variables/expressions. Any ideas?
In cases when a closure is embedded inside a GString, toString()
is not called on the closure unlike variables embedded in GString. In the above case where you see an error, owner
is the surrounding closure and toString()
would not be called on the closure.
To get around with it, toString()
has to be called explicitly on owner
like:
({ ({ println "${owner.toString()}" })() })()
The same is applicable for as many nested level of closures we build.
({ ({ ({ println "${owner.toString()}" })() })() })()
With proper indentation it would look like:
({
({
({
println "${owner.toString()}"
})()
})()
})()
The behavior can be explained with a trivial example.
def clos = {return {"hello"}}
println "${clos()}" //prints nothing
println "${clos()()}" //prints hello
Explanation for error:-
Now coming to the error that is faced, as mentioned earlier, when a closure is embedded inside GString, toString()
is not called on the closure. Instead the closure is called and then toString()
is invoked/applied on the result of the called closure. Which means:
"$owner"
is equivalent to owner().toString()
.
In the above case, while calling the outer closure it eventually calls itself through the GString implementation ["$owner"
] and the call grows as a recursion, hence a stackoverflow error.
Note:
You can omit {}
when the variable on which you are applying GString is simple. "${myVariable}"
is same as "$myVariable"
. You can do that as long as you are accessing simple properties of the variable. "$myVariable.class.name"
is good (as long as myVariable is not a map). But when method calls are involved curly braces are needed "${myVariable.toString()}"