Groovy Closures and Java Functional Interfaces: Does SAM Coercion Preserve Delegate-Based Method Resolution?
0 reputation · 03 Jun 2022, 21:18 UTC
0 reputation · 03 Jun 2022, 21:18 UTC
When a Groovy closure is exposed to Java code through a functional interface such as java.util.function.Function or Consumer, the conversion can be performed with the as operator or under @CompileStatic. The resulting Java SAM type hides the closure’s Groovy-specific owner and delegate properties from the Java caller, but it is unclear whether the underlying groovy.lang.Closure instance continues to resolve method calls against its current delegate after the coercion has occurred.
In dynamic Groovy, reassigning a closure’s delegate at runtime changes method resolution and is central to DSL behavior. If a Java consumer invokes the SAM method after that reassignment, the expected outcome depends on whether the adaptation layer captures the original owner context or forwards calls through the live closure object. The distinction matters for libraries that accept Groovy callbacks and later mutate their delegates, especially when the same closure instance is shared across Groovy and Java boundaries.
Does coercing a Groovy closure to a Java functional interface preserve the live delegate lookup for subsequent SAM invocations? If the delegate is mutated after conversion, does the Java caller observe the new resolution target or the original owner? Are these semantics stable across Groovy 3 and Groovy 4 without additional annotations?
33275 reputation · 03 Jun 2022, 23:21 UTC
Yes. Coercing a Groovy Closure to a Java SAM type with as or under @CompileStatic preserves live delegate-based method resolution. The SAM adapter holds a reference to the live Closure instance and forwards each SAM invocation to Closure.call at invocation time. If the Closure’s delegate is reassigned after coercion, subsequent calls made through the Java functional interface observe the new delegate.
call method, not a snapshot of the Closure’s state.delegate, owner and thisObject at the moment of invocation.The adapter generated for a SAM type is a thin wrapper. It keeps a reference to the original Closure object. Each SAM call is translated to a call to Closure.doCall with the SAM arguments. Because doCall performs delegate lookup dynamically, any mutation of closure.delegate or closure.resolveStrategy after coercion is visible to the Java caller.
java.util.function.Supplier or Function, via as.closure.delegate = newTarget and invoke the SAM method again. The result should reflect the new target.Notes and limits
@CompileStatic, static type checking can reject calls that rely only on delegate-only methods unless the parameter types or @DelegatesTo are declared. Runtime delegate lookup still works.One diagnostic detail that changes the recommendation: are you changing the delegate via setDelegate with a resolveStrategy that includes DELEGATE_FIRST, or are you rehydrating the Closure with rehydrate? The resolve strategy determines whether owner methods are still reachable after mutation.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.