skip to content

Given a mix of types (int, String, List, List<?>, List<String>, List<? extends Number>, String[], List<String>[]), classify each as reifiable or non-reifiable and justify.

level: seniorimportance: should knowfreq 35%

answer

  1. One rule: nothing lost to erasure
  2. Reifiable: primitive, non-generic, raw, all-? params, array of reifiable
  3. Bounded wildcard (? extends/super) = NON-reifiable
  4. Array reifiability follows the component type
  5. List<String>[] non-reifiable -> can't new it

basics

~20 s

Reifiable: int, String, List (raw), List<?>, String[]. Non-reifiable: List<String>, List<? extends Number>, List<String>[]. The rule: a type is reifiable if it loses nothing to erasure — primitives, non-generic types, raw types, unbounded-wildcard parameterizations, and arrays of reifiable types.

solid answer

~40 s

Applying the JLS rule (reifiable = full type known at runtime after erasure): - `int` — reifiable (primitive). - `String` — reifiable (non-generic class). - `List` (raw) — reifiable (raw type, no arguments to lose). - `List<?>` — reifiable (unbounded wildcard loses nothing). - `List<String>` — non-reifiable (concrete argument erased). - `List<? extends Number>` — non-reifiable (bounded wildcard carries info erasure drops). - `String[]` — reifiable (array whose component String is reifiable). - `List<String>[]` — non-reifiable (array of a non-reifiable component; this is why you can't `new` it). The single decision procedure: strip to what survives erasure; if that equals the source type, it's reifiable. Arrays inherit reifiability from their component type.

code

java · 10 lines
java
// Reifiable (allowed in instanceof / array creation):
Object o = "x";
boolean a = o instanceof List<?>;      // OK
String[] sa = new String[3];           // OK
List<?>[] wa = new List<?>[3];          // OK (component reifiable)

// Non-reifiable (rejected):
// boolean b = o instanceof List<String>;       // compile error
// boolean c = o instanceof List<? extends Number>; // compile error
// List<String>[] bad = new List<String>[3];    // generic array creation error

go deeper

for a junior

Can identify the obvious cases: primitives and String are 'real', List<String> isn't fully known at runtime.

for a middle

Applies the checklist to most cases, including raw List and List<?> being reifiable.

for a senior

Confidently handles the tricky pairs (List<?> vs List<? extends Number>) and array recursion (String[] vs List<String>[]) and justifies each.

for a principal

Uses classification fluently to predict compiler behavior and to design APIs that avoid non-reifiable pitfalls (e.g. wildcard parameters, collection-over-array).

## The one rule, then apply it A type is **reifiable** if its complete type information is available at runtime — i.e. nothing is removed by **type erasure** (the compile-time removal of generic type arguments). The JLS spells this out as a checklist; a type is reifiable iff it is one of: a primitive, a non-generic class/interface, a raw type, a parameterization where **every** argument is an **unbounded wildcard** (`?`), or an **array whose component type is reifiable**. Let's classify each example and justify it. ### Reifiable - **`int`** — a primitive. No generics involved; the JVM knows it exactly. ✅ - **`String`** — a non-generic class. There are no type arguments to erase. ✅ - **`List`** (the **raw** type, written with no `<...>`) — a raw type is explicitly reifiable. There are no arguments, so nothing is lost; runtime sees `List`, source says `List`. ✅ - **`List<?>`** — parameterized, but the only argument is an **unbounded wildcard**. `?` means 'some unknown type', which is *exactly* what the runtime knows after erasure. Nothing specific is lost, so it's reifiable. ✅ - **`String[]`** — an array. An array is reifiable iff its **component type** is reifiable. `String` is reifiable, so `String[]` is reifiable; at runtime the array genuinely knows it's a `String[]` (and enforces it via the store-check). ✅ ### Non-reifiable - **`List<String>`** — the concrete argument `String` is **erased**; at runtime it's just `List`. The runtime type is less specific than the source type → non-reifiable. ❌ - **`List<? extends Number>`** — a **bounded** wildcard. Unlike `?`, `? extends Number` constrains the argument, and that constraint is lost to erasure (runtime just sees `List`). So it's non-reifiable. ❌ (Likewise `List<? super Integer>`.) - **`List<String>[]`** — an array whose **component** type `List<String>` is non-reifiable. Reifiability of an array follows its component, so this array is non-reifiable. This is precisely why `new List<String>[10]` is a compile error. ❌ ## The decision procedure (use this on any type) 1. Primitive? → reifiable. 2. No type arguments at all (non-generic or raw)? → reifiable. 3. Parameterized: are **all** arguments `?` (unbounded)? → reifiable; any concrete or *bounded* argument → non-reifiable. 4. Array? → reifiable iff its component type is reifiable (recurse). 5. A type variable `T`? → non-reifiable. ## Why bother classifying The answer to 'reifiable?' directly predicts what the compiler will allow: you can `new` an array of it, use it with `instanceof`, and cast to it without an unchecked warning **iff it's reifiable**. So classification is the practical skill that lets you predict generics compile errors before the compiler tells you.

  • Is Map<?,?> reifiable?
    Yes. Every argument is an unbounded wildcard, so nothing specific is lost to erasure — it's reifiable, like List<?>.
  • Why is List<? extends Number> non-reifiable when List<?> is reifiable?
    List<?> means 'some unknown type' which matches what runtime knows after erasure. The bound in '? extends Number' adds information that erasure discards, so it can't be fully recovered at runtime.

saying these in an interview costs you the question

  • Lumping List<? extends Number> in with List<?> — bounded is non-reifiable, unbounded is reifiable
  • Saying String[] is non-reifiable because arrays involve generics — arrays of reifiable components are reifiable
  • Calling raw List non-reifiable — raw types are explicitly reifiable
  • Forgetting array reifiability is recursive on the component type

context