Annotation Interface ClassTag
Class parameter as compiler-supplied under
static compilation. On the JVM, a method that needs the runtime type of a generic type
parameter cannot recover it (erasure), so the API must take an explicit Class<T>
token. That token is redundant with a type the compiler already knows — the receiver's
type argument. @ClassTag removes the redundancy.
Under @CompileStatic / @TypeChecked, when a call omits the Class token
argument(s), the type checker may select an overload whose parameters, beyond those matching the
supplied arguments, are all @ClassTag Class<X> and synthesise the X.class
argument(s) from the receiver's matching type argument(s), rewriting the call. Each
@ClassTag parameter is filled at its declared position — anywhere in the parameter
list, not just trailing — and the caller's supplied arguments map to the remaining
(non-tag) parameters in order.
As an example, asChecked declares an explicit token:
public static <T> List<T> asChecked(List<T> self, @ClassTag Class<T> type)which lets statically-typed callers omit it:
List<String> list = [].asChecked() // compiler injects String.class
Selection is normally additive: the tagged overload is chosen only when the call as
written matches no method (as above — there is no token-less asChecked()). When a
token-less overload also matches, choosing the tagged one instead (preemption) changes
the meaning of existing source, so three conditions gate it:
- Intent — the API author must declare the tagged overload preemptive with
preempt=true; without it the overload is only ever selected additively. Groovy's own checkedwithDefaultoverloads declare it (their lenient sibling predates them and could silently corrupt a typed map's key set). - Containment — a tagged overload may only preempt a token-less overload declared by the same class (or, for instance methods, the same class hierarchy). A library can upgrade callers of its own lenient API but can never capture calls owned by another library, so adding a jar to the compile classpath cannot re-route existing calls it does not already own.
- Consent — the consumer may veto all preemption globally via
CompilerConfiguration.setClassTagPreemptionDisabled(boolean)(also seeded from thegroovy.classtag.preemption.disablesystem property). Code compiled with the veto behaves exactly as it did before any library declared preemption; a finer-grained (per-method or per-call-site) opt-out may be added later if experience shows the need.
Boundaries:
- Static only. Dynamic Groovy ignores the annotation and binds the original overload; the shorter (token-less) spelling is static-only syntax. If no token can be synthesised there is no zero-token method to bind, so the call fails to resolve.
- The type variable must be statically known.
List<String>works;def, rawList, and wildcard-only types do not — no token is synthesised. - Receiver type arguments only. A type variable declared by the method itself (which shadows any like-named class variable) cannot be reified from the receiver; tagging one on an instance method compiled from source is a compile-time error. Static methods are exempt: in the extension-method authoring pattern the type variable is necessarily method-declared and connects to the receiver through the self parameter.
- Erased fidelity. The synthesised
Class<X>reifies only the erased class:List<String>andList<Integer>are indistinguishable. - Nothing-to-gain guard. When every token would erase to
Object(e.g. aList<Object>receiver), no token is synthesised — a checkedObjectview could never reject anything — so an additive call fails to resolve and a preemptive upgrade is skipped (the lenient overload is kept). - Escape hatch. Passing the
Classexplicitly always works, in every mode.
- Since:
- 6.0.0
-
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionbooleanDeclares that the overload carrying this parameter may preempt a matching token-less overload declared by the same class (or class hierarchy): an existing call that binds the token-less overload is transparently upgraded to this one under static compilation.Optionally names the type variable to reify, for when the parameter type cannot carry it (e.g.
-
Element Details
-
value
String valueOptionally names the type variable to reify, for when the parameter type cannot carry it (e.g. a raw or wildcardClasstoken, or a method whose own type variables do not line up with the receiver's). When empty (the default), the type variable is read from the parameter typeClass<X>.Note:
foris a Java reserved word and cannot be an annotation member name, so the override is spelled@ClassTag("K")(or@ClassTag(value = "K")). For a method compiled from source in the same unit, the static type checker rejects a name that does not resolve to a type variable in scope, so typos fail at compile time. The check cannot reach an extension method supplied by an already-compiled library (it is never visited as source); such a typo silently disables injection rather than being reported.- Returns:
- the name of the type variable to reify, or empty to read it from the parameter type
- Default:
- ""
-
preempt
boolean preemptDeclares that the overload carrying this parameter may preempt a matching token-less overload declared by the same class (or class hierarchy): an existing call that binds the token-less overload is transparently upgraded to this one under static compilation. Because that changes the meaning of existing source, it is off by default; without it the overload is only selected additively (when the call as written matches no method). Consumers retain a veto viaCompilerConfiguration.setClassTagPreemptionDisabled(boolean). By convention, set it on every@ClassTagparameter of the overload (any one suffices to declare the intent).- Returns:
- whether this overload may preempt its same-owner token-less sibling
- Default:
- false
-