At runtime, what does List<?> look like, and how does it relate to type erasure and operations like instanceof?
answer
- Erasure: type args gone in bytecode
- All List<X> share one runtime class List
- Only instanceof List<?> (or raw) is legal
- List<?> is reifiable; List<String> is not
- new List<?>[] legal; new List<String>[] illegal
basics
~10 sAt runtime Java erases generic type information, so List<String> and List<Integer> are both just List. Because of that, the only generic instanceof check Java allows is the unbounded wildcard form: obj instanceof List<?>.
solid answer
~40 sJava generics use type erasure: type arguments exist only at compile time and are removed in bytecode, so all parameterizations of List share the single runtime class java.util.List. List<String>.class is not a thing - there is only List.class. This is why you cannot write obj instanceof List<String> (the runtime has no way to check the element type); the only legal generic instanceof and the only legal cast target of that shape is the unbounded wildcard: obj instanceof List<?>, because <?> adds no runtime information to check. Similarly you can write a generic array type only as the unbounded-wildcard reifiable type, e.g. List<?>[]. The unbounded wildcard is special precisely because it is the parameterized form that survives erasure unchanged - it asserts nothing about the element type, which is exactly what the runtime can verify.
go deeper
Knows generics are erased at runtime and that you can write instanceof List<?> but not instanceof List<String>.
Explains erasure means all List parameterizations share one runtime class and that the wildcard is the only legal generic instanceof form.
Connects reifiability, erasure, instanceof, casts, and array creation, explaining why List<?> is the runtime-honest form.
Reasons about erasure tradeoffs (compatibility vs reification) and guides patterns (e.g. type tokens) for code that needs runtime type information generics cannot provide.
## Type erasure: the runtime forgets type arguments Java implemented generics with **type erasure**: generic type information is used by the compiler for checking, then **erased** from the compiled bytecode. After compilation: ``` List<String> -> List List<Integer> -> List List<?> -> List ``` All of them are the **same** runtime class, `java.util.List`. There is no `List<String>.class`; there is only `List.class`. Two variables `List<String>` and `List<Integer>` will even report `a.getClass() == b.getClass()` as `true`. ## A type is 'reifiable' if its full type is available at runtime A **reifiable** type is one whose type information is fully present at runtime. `String`, `Integer`, `List` (raw), and `List<?>` are reifiable. `List<String>` is **not** reifiable, because the `String` part was erased. ## Why `instanceof List<String>` is illegal The `instanceof` operator is a runtime check. To evaluate `obj instanceof List<String>`, the JVM would need to inspect the element type at runtime - but erasure deleted it. So the language **forbids** `instanceof` (and casts) against non-reifiable parameterized types: ``` if (obj instanceof List<String>) { } // COMPILE ERROR if (obj instanceof List<?>) { } // OK if (obj instanceof List) { } // OK (raw, but prefer the wildcard) ``` The **only** generic form allowed with `instanceof` is the **unbounded wildcard** `List<?>` (and the raw type, which you should avoid). It works because `<?>` asserts nothing about the element type - there is nothing for the runtime to check beyond 'is it a List?', which is exactly what is reifiable. ## Generic arrays and the wildcard Array creation has the same constraint. You cannot create `new List<String>[10]` (generic array creation is illegal, again because of erasure + array covariance unsoundness). But the unbounded-wildcard array type `List<?>[]` **is** reifiable, so `new List<?>[10]` is legal. This is another place the unbounded wildcard is the special, runtime-safe form. ## Casting Casting to `(List<String>)` compiles but only with an **unchecked** warning, because the cast cannot actually be verified at runtime - the `<String>` part is unenforceable. Casting to `(List<?>)` is fully checkable. So the wildcard is again the honest, runtime-truthful form. ## Why this matters The unbounded wildcard is not just an API-design convenience; it is the **parameterized type that matches what the runtime actually knows**. Whenever you need a generic type in a runtime-sensitive position - `instanceof`, a cast you want checked, an array type - the unbounded wildcard is the form the language permits, precisely because erasure leaves it with no extra claims to verify.
- What is a reifiable type and is List<?> one?A reifiable type has its complete type information available at runtime. List<?> is reifiable (the wildcard adds nothing to check), but List<String> is not, because the element type is erased.
- Why can you create new List<?>[10] but not new List<String>[10]?Generic array creation requires a reifiable component type. List<?> is reifiable so the array is allowed; List<String> is not reifiable, so its array creation is forbidden to keep the type system sound.
saying these in an interview costs you the question
- Thinking obj instanceof List<String> is allowed - it is a compile error
- Believing List<String>.class exists - only List.class does
- Assuming a cast to List<String> is runtime-checked - it is unchecked
- Confusing 'reifiable' so that List<String> is treated as fully runtime-typed