At runtime, how is `List<? extends Number>` represented, and what does that imply for reflection, instanceof, and array creation?
answer
- erasure: List<? extends Number> → raw List
- instanceof only List or List<?>, not bounded
- no generic/bounded-wildcard array creation
- unchecked-cast warning + heap pollution
- reflection: WildcardType.getUpperBounds() from signature
basics
~20 sGenerics, including the wildcard, are erased at compile time. At runtime there is just List — the ? extends Number part is gone. So you can't instanceof List<? extends Number> it, and you can't create generic arrays of it.
solid answer
~50 sJava generics use type erasure: after compilation, `List<? extends Number>` becomes the raw `List` (the wildcard and bound vanish), and the compiler inserts casts where needed. Consequences: `obj instanceof List<? extends Number>` is illegal — you can only test `instanceof List<?>` because the element type isn't available at runtime. You can't do `new (List<? extends Number>)[10]` reified-array creation; generic array creation is forbidden to preserve store-type safety. Reflection still sees the wildcard at the *declaration* level: the bound survives in signature metadata, so `Method.getGenericParameterTypes()` can return a `WildcardType` whose upper bound is `Number`, even though instances carry no such info. So the wildcard is a compile-time and signature-metadata concept; at the instance level only the erasure (`List`, elements as `Number`/`Object` via the bound) exists. This is why heap pollution and unchecked-cast warnings exist at all.
code
java · 21 linesimport java.lang.reflect.*;
import java.util.List;
class C {
void take(List<? extends Number> nums) {}
}
public class Demo {
public static void main(String[] a) throws Exception {
// Runtime instance: erasure only.
List<? extends Number> l = List.of(1, 2);
System.out.println(l instanceof List<?>); // true
// System.out.println(l instanceof List<? extends Number>); // won't compile
// Reflection sees the wildcard from the DECLARATION signature.
Method m = C.class.getDeclaredMethod("take", List.class);
ParameterizedType pt = (ParameterizedType) m.getGenericParameterTypes()[0];
WildcardType w = (WildcardType) pt.getActualTypeArguments()[0];
System.out.println(w.getUpperBounds()[0]); // class java.lang.Number
}
}go deeper
Aware that generics 'disappear' at runtime and that List<? extends Number> is just List then.
Explains erasure to the raw type, why instanceof of a parameterized type is illegal, and that casts are unchecked.
Connects erasure to heap pollution, generic array creation bans, and unchecked warnings, and knows List<?>/raw are the only instanceof forms.
Distinguishes instance erasure from declaration signature metadata, uses reflection WildcardType.getUpperBounds(), explains the super-type-token pattern, and the historical/backward-compat rationale for erasure.
## Type erasure: the core fact Java implemented generics in 2004 with **type erasure** to stay backward-compatible with pre-generics bytecode. The compiler uses generic types for **compile-time** checking, then **erases** them: - A type parameter `T` with no bound erases to `Object`; `T extends Number` erases to `Number`. - A parameterized type like `List<Integer>` or `List<? extends Number>` erases to the **raw type** `List`. - The compiler inserts synthetic **casts** at use sites so the code still behaves as typed. So `List<? extends Number>` at runtime is simply `java.util.List`. The wildcard, the bound, and the element type are not stored in the object. There is no per-instance "I am a List of Numbers" tag — generics are **non-reified**. ## Implication 1: `instanceof` is restricted Because an instance carries no element-type info, you cannot ask about it: ```java if (x instanceof List<? extends Number>) { ... } // COMPILE ERROR if (x instanceof List<?>) { ... } // OK (unbounded wildcard only) ``` The only generic forms allowed in `instanceof` are the raw type `List` and the unbounded wildcard `List<?>`, both of which test exactly the same thing at runtime ("is it a `List`?"). A bounded wildcard would imply a runtime element-type check that erasure cannot perform. ## Implication 2: no generic array creation ```java List<? extends Number>[] arr = new List<? extends Number>[10]; // COMPILE ERROR List<?>[] ok = new List<?>[10]; // allowed (unbounded) ``` Arrays are **reified** (they know their component type at runtime and throw `ArrayStoreException` on a bad store), but generics are erased — combining them would let you defeat the array store check and pollute the heap silently. So Java forbids creating arrays of concrete or bounded-wildcard parameterized types. Arrays of the unbounded wildcard (`List<?>[]`) are allowed because they make no element-type promise. ## Implication 3: casts are unchecked ```java Object o = getSomeList(); List<? extends Number> l = (List<? extends Number>) o; // unchecked warning ``` The cast can only verify `o` is a `List` at runtime; the `? extends Number` part is unverifiable, so the compiler emits an *unchecked* warning. If `o` were really a `List<String>`, you'd have **heap pollution** that surfaces as a `ClassCastException` only when an element is later read as `Number`. ## Implication 4: reflection DOES see the wildcard — at the declaration level Erasure removes generics from *instances*, but the **signature attribute** in the class file preserves generic information about *declarations* (method parameters, fields, supertypes). So: ```java Method m = C.class.getMethod("take", List.class); Type t = m.getGenericParameterTypes()[0]; // ParameterizedType List<? extends Number> ParameterizedType pt = (ParameterizedType) t; Type arg = pt.getActualTypeArguments()[0]; // WildcardType Type[] upper = ((WildcardType) arg).getUpperBounds(); // [ Number ] ``` The `java.lang.reflect.WildcardType` interface exposes `getUpperBounds()` (and `getLowerBounds()` for `? super`). This is how frameworks (Jackson, Spring) recover generic types via `TypeToken`/`ParameterizedTypeReference` tricks — they read the *declared* signature, not the erased instance. ## Putting it together The upper-bounded wildcard lives in **two** places: (1) the compiler's type checker (where it enforces covariance and the producer rules) and (2) class-file signature metadata (where reflection can read it). It does **not** live in the runtime identity of objects — there only the erasure (`List`, with elements viewed as the bound) exists. Every "weird" generics rule — `instanceof` limits, no generic arrays, unchecked-cast warnings, heap pollution — traces back to this single design choice of erasure. ## Why it was done this way Erasure let generics be added without changing the JVM or breaking the millions of pre-2004 class files and raw-type call sites. The cost is the non-reification limits above. Project Valhalla and related efforts have explored reified generics, but classic erasure remains the model.
- Why are arrays of bounded-wildcard parameterized types forbidden but `List<?>[]` allowed?Reified arrays check component types at store time; a bounded generic array would promise an element type erasure can't enforce, enabling silent heap pollution. The unbounded `List<?>` makes no such promise, so it's allowed.
- How do libraries like Jackson capture `List<? extends Number>` despite erasure?Via the 'super type token' trick: an anonymous subclass embeds the generic type in its class-file signature, and reflection reads it through `getGenericSuperclass()` / `WildcardType`.
saying these in an interview costs you the question
- Claiming you can do `instanceof List<? extends Number>`.
- Saying generics are fully reified like arrays.
- Thinking reflection can't recover any generic info — it can, from declaration signatures.
- Believing you can create `new List<? extends Number>[n]`.
- Confusing instance-level erasure with declaration-level signature metadata.