public abstract class PackedClosure
extends Closure
The shared Closure adapter family for @PackedClosures compact closure
compilation.
Normally the Groovy compiler generates one inner class per closure literal
(e.g. Owner$_method_closure1). Under @PackedClosures an eligible
closure body is instead hoisted into a synthetic method on the enclosing ("owner")
class, and the closure literal is replaced by an instance of the fixed-arity family
member matching its declared parameter count (Fixed0..Fixed4,
FixedIt for implicit-parameter literals, FixedN for higher and vararg
arities), which dispatches back to that method. This removes the per-closure generated
class (and the deeply-nested $_closure1$_closure2$_closure3 name explosion)
while still yielding a real groovy.lang.Closure instance, so features that
operate through call() (iteration, curry, memoize,
trampoline) continue to work — and the family member's declared doCall
signature(s) give class-level introspection (SAM-overload selection, MOP method
selection) exactly the view a generated closure class would present.
Dispatch goes through the hosting class's GeneratedDispatcher tables: the adapter
holds the class's shared dispatchers, the hoisted method's compile-time id, and its own
dispatch receiver (captured at construction, so dehydrate()/rehydrate()
leave the closure callable). A target taking one or two values beyond the receiver — the
overwhelmingly common closure shapes — dispatches through the array-free per-arity tables,
passing the receiver, captured values and arguments as plain parameters; anything else
packs [receiver, captured..., args...] into one array for the general table.
Either way the chain is ordinary, JIT-friendly bytecode — no reflection, and no
per-instance MethodHandle (which the JIT cannot constant-fold, making its invoker
path many times slower than a direct call). The id binds the exact hoisted method, so an
inherited packed closure can never misdispatch to a same-named method a subclass happens
to declare. Argument adaptation on every route replicates metaclass dispatch on a
generated closure class: arity mismatches raise MissingMethodException, a single
List destructures across a non-one-parameter signature, trailing-array parameters
vararg-collect, and per-parameter coercion follows the metaclass compatibility rules
(including Closure-to-SAM).
| Modifiers | Name | Description |
|---|---|---|
static class |
PackedClosure.Fixed0 |
Fixed-arity members of the adapter family: the compiler instantiates the member matching the
literal's declared parameter count (FixedN serves higher arities), so a packed
closure's arity is visible to class-level introspection exactly as on a generated closure
class — notably MetaClassHelper's SAM-overload disambiguation, which reflects the
argument class's declared doCall and cannot consult the instance. |
static class |
PackedClosure.Fixed1 |
|
static class |
PackedClosure.Fixed2 |
|
static class |
PackedClosure.Fixed3 |
|
static class |
PackedClosure.Fixed4 |
|
static class |
PackedClosure.FixedIt |
The implicit-parameter literal ({ it * 2 }): a generated closure class declares both
doCall() and doCall(Object) for it, which class-level introspection reads as
the fuzzy "0 or 1" arity that lets the closure match both zero- and one-parameter SAM
overloads (GROOVY-10905) — so this member declares the same pair. |
static class |
PackedClosure.FixedN |
Arities above four (rare) and vararg-shaped literals (trailing array parameter, e.g. |
| Fields inherited from class | Fields |
|---|---|
class Closure |
DELEGATE_FIRST, DELEGATE_ONLY, DONE, IDENTITY, OWNER_FIRST, OWNER_ONLY, TO_SELF, maximumNumberOfParameters, parameterTypes |
| Constructor and description |
|---|
PackedClosure(Object owner, Bundle dispatchers, int id, String method, Object[] captured, Class[] visibleTypes, boolean strict)
|
| Type Params | Return Type | Name and description |
|---|---|---|
|
public final Object |
call(Object args) |
|
public final Object |
call(Object arguments) |
|
public static PackedClosure |
create(Object owner, Bundle dispatchers, int id, String method, Object[] captured, Class[] visibleTypes, boolean strict, boolean implicit, boolean vararg)The single entry point for creating a packed closure, reached from generated bytecode via ScriptBytecodeAdapter.packedClosure (so neither this class nor the fixed-arity family members appear on the emitted-bytecode surface). |
|
public final Object |
dispatchAll(Object[] args)The single dispatch entry every route funnels into: the call lanes, the
Fixed* subclasses' arity-true doCalls, and the
PackedClosureMetaClass short-circuit. |
|
public void |
setDelegate(Object delegate) |
|
public void |
setResolveStrategy(int resolveStrategy) |
|
public String |
toString()Names the hoisted body, so diagnostics identify the literal the way a generated class's name would ( Script$_run_closure1 becomes {@code Script. |
| Methods inherited from class | Name |
|---|---|
class Closure |
andThen, andThenSelf, andThenSelf, asWritable, call, call, call, checkForReferenceCycle, clone, compose, composeSelf, composeSelf, curry, curry, dehydrate, getDelegate, getDirective, getMaximumNumberOfParameters, getOwner, getParameterTypes, getProperty, getResolveStrategy, getThisObject, isCase, leftShift, leftShift, memoize, memoizeAtLeast, memoizeAtMost, memoizeBetween, mopUnperturbed, ncurry, ncurry, rcurry, rcurry, rehydrate, rightShift, run, setDelegate, setDirective, setMetaClass, setProperty, setResolveStrategy, throwRuntimeException, trampoline, trampoline |
class GroovyObjectSupport |
getMetaClass, setMetaClass |
owner - the enclosing instance the hoisted method lives on (also used as thisObject;
the enclosing class itself for a hoisted method in a static context)dispatchers - the hosting class's shared dispatch tables (see GeneratedDispatcher)id - the hoisted method's compile-time-assigned index in those tablesmethod - the name of the hoisted synthetic method (kept for diagnostics only)captured - values captured from the enclosing scope, passed before the call arguments
on every dispatch (a written capture passes its shared
groovy.lang.Reference unchanged, so writes still propagate)visibleTypes - the closure's declared parameter types, so callers that key behaviour on
getParameterTypes() (DGM arity/type decisions) and argument
adaptation (vararg collection into a trailing array parameter) behave
exactly as with a generated closure classstrict - whether the delegate guard throws. true for the dynamic trust path
(an unverifiable @PackedClosures assertion must fail fast on misuse);
false when the type checker PROVED every free name owner-resolved. The single entry point for creating a packed closure, reached from generated bytecode via
ScriptBytecodeAdapter.packedClosure (so neither this class nor the fixed-arity family
members appear on the emitted-bytecode surface). The runtime shape switch is negligible
against the object-creation cost. implicit marks an implicit-it literal
(fuzzy 0/1 arity), vararg a trailing-array parameter; both need the varargs
doCall that only FixedIt/FixedN declare.
The single dispatch entry every route funnels into: the call lanes, the
Fixed* subclasses' arity-true doCalls, and the
PackedClosureMetaClass short-circuit. Deliberately NOT named doCall: a
public varargs doCall(Object[]) inherited by every family member would, under
MOP-routed dispatch, exact-match a single Object[] argument, beat
doCall(Object), and spread the array — a generated closure class declares no such
method, so a classed closure never spreads.
Names the hoisted body, so diagnostics identify the literal the way a generated class's
name would (Script$_run_closure1 becomes Script.$packed$closure$0).
Copyright © 2003-2026 The Apache Software Foundation. All rights reserved.