Package groovy.transform
Annotation Interface PackedClosures
Opt-in for compact closure compilation: within the annotated class or method, an eligible closure
literal's body is hoisted into a synthetic method on the enclosing class and the literal is replaced
by a shared
PackedClosure adapter, instead of generating one
inner class per closure. The value stays a real groovy.lang.Closure, so curry,
memoize, trampoline and iteration keep working; captured values are threaded by
value (read-only) or via a shared groovy.lang.Reference (when written), so a packed closure
behaves identically to the class-based form.
Packing is best-effort: a closure that cannot be packed is compiled exactly as today (a generated closure class), so the annotation is always safe to add. A closure is kept as a class when it:
- references
owner,delegate,thisObject,resolveStrategy,directive,metaClassorsuper— it needs a realClosure; - has default parameter values, or contains an anonymous inner class;
- is nested inside another closure, or visibly escapes its method (returned, stored to a field/property/index, initialising a field, appended, or placed in a collection literal);
- is visibly serialization-bound (cast or coerced to a
Serializabletype, passed directly to awriteObjectcall, or held in a local that is) — it keeps its class so serialization works; - is written where the adapter cannot stand in for a generated class: as an argument to a
this(...)/super(...)constructor call, inside a trait, or cast to an intersection type; - under dynamic compilation, resolves any free name — an implicit-this call, a bare
field/property-bound or dynamic variable, or an explicit
this-property — that the delegate chain or the MOP could intercept; - under
@CompileStatic, resolves a name against a delegate (e.g. via@DelegatesToorwith), is left to runtime resolution by a type-checking extension (mixed-mode dynamic islands), or touchesthis-properties of aMap-implementing owner — packing is otherwise proven sound from the type checker's resolution.
PackedClosure adapter then fails fast if a delegate, or a delegate-consulting
resolveStrategy, is later set on a packed closure.
Automatic packing of provably-safe @CompileStatic closures — without this annotation — is
available behind the groovy.target.closure.pack flag. Use mode() to have declines
reported (or to opt a scope out); on the un-annotated flag path,
groovy.target.closure.pack.report=true reports every decline with its reason as a
compiler warning — the operational way to see where the packability boundary falls in a codebase.
Because every packed closure is an instance of a small fixed-arity adapter family
(PackedClosure$Fixed0..4, $FixedIt, $FixedN) rather than its own class,
three differences from the class-based form remain that cannot, in general, be detected at
compile time (the visibly serialization-bound cases above are the detectable exception):
- Serialization: a packed closure is not serializable — attempting it fails fast
at runtime with a message naming the closure and this opt-out (note
dehydrate()cannot make it serializable — the dispatch state remains — although a dehydrated packed closure stays callable), so scopes that serialize their closures should not be packed; - Class identity:
closure.getClass()distinguishes arity (enough for class-level introspection such as SAM-overload selection) but not individual literals, so code keyed on per-closure generated class names or types will not find them; - Class-level metaclass changes: modifying a family member's metaclass affects
every packed closure of that arity, where a per-closure-class change was scoped to one
literal. Per-instance
setMetaClassis fully honoured — a packed closure whose metaclass has been replaced or wrapped routes all dispatch through it, exactly as a generated closure class does.
- See Also:
-
Nested Class Summary
Nested Classes -
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionThe packing behaviour of this scope:PackedClosures.PackMode.LENIENT(pack, silent on declines, the default),PackedClosures.PackMode.WARN(pack, a compiler warning per decline with the reason),PackedClosures.PackMode.STRICT(pack, a compiler error on any decline), orPackedClosures.PackMode.DISABLED(do not pack).
-
Element Details
-
mode
The packing behaviour of this scope:PackedClosures.PackMode.LENIENT(pack, silent on declines, the default),PackedClosures.PackMode.WARN(pack, a compiler warning per decline with the reason),PackedClosures.PackMode.STRICT(pack, a compiler error on any decline), orPackedClosures.PackMode.DISABLED(do not pack). The most-specific declaration wins, so aDISABLEDmethod opts out of a packed class (andDISABLEDalso overrides the automaticgroovy.target.closure.packflag); the WARN/STRICT diagnostics apply only to the annotated scope, never to the flag.- Default:
- LENIENT
-