What concrete operations does type erasure forbid or complicate, and what are the standard workarounds?
answer
- No new T[], no T.class, no instanceof List<String>
- Same-erasure overload clash
- No static field of type T; no catch(T e)
- Unchecked casts + heap pollution / @SafeVarargs
- Fix = pass Class<T> token or super type token
basics
~20 sBecause generic types are gone at runtime you cannot do new T[], T.class, or instanceof List<String>, and you cannot overload methods that differ only by type argument. Workarounds: pass a Class<T> token, use reflective array creation, or use a super type token.
solid answer
~50 sErasure forbids any operation that needs the type argument at runtime. You cannot create a generic array (`new T[n]`) or an array of a parameterized type, because the JVM checks array stores by runtime element type and the parameter is erased. You cannot use a type-parameter class literal (`T.class`) or test `instanceof List<String>` (only `List<?>`). Two methods whose signatures differ only by type argument have the *same erasure*, so they fail to compile. Static fields cannot be of a type-parameter type. Standard workarounds: pass a `Class<T>` token and use `clazz.cast(...)` or `Array.newInstance(clazz, n)` for runtime-typed arrays; use a *super type token* for full generic types; suppress and isolate unchecked casts behind a checked API. Also beware *heap pollution*: erasure lets a `List<String>` reference alias a list actually holding non-Strings via raw types, deferring the `ClassCastException` to read time.
code
java · 23 linesimport java.lang.reflect.Array;
import java.util.*;
final class TypedStack<T> {
private final Class<T> type; // the type, smuggled in as a value
private T[] data;
private int size;
@SuppressWarnings("unchecked")
TypedStack(Class<T> type, int cap) {
this.type = type;
this.data = (T[]) Array.newInstance(type, cap); // legal generic array via reflection
}
void push(T item) { data[size++] = type.cast(item); } // runtime-checked, unlike erased cast
T pop() { return data[--size]; }
public static void main(String[] a) {
TypedStack<String> s = new TypedStack<>(String.class, 4);
s.push("x");
System.out.println(s.pop()); // x
}
}go deeper
Knows you cannot do new T[] or T.class and that List<String> and List<Integer> look the same at runtime.
Lists the main restrictions (arrays, class literal, instanceof, same-erasure overloads) and applies the Class<T> token / Array.newInstance workaround.
Adds heap pollution, @SafeVarargs, checked collections, narrow unchecked-cast suppression, and explains each restriction from the reified-arrays-vs-erased-generics tension.
Designs erasure-safe generic APIs, reasons about where to localize unsafe casts, when to expose Class/Type tokens, and the soundness trade-offs of covariant arrays vs invariant generics.
## Why these restrictions exist Every restriction below comes from the same root cause: **at runtime the type argument is gone** (see the erasure topic). Any language feature that would need to *inspect or act on* that argument at runtime must therefore be forbidden or rerouted. ## 1. No `new T[]` and no `new List<String>[]` Arrays in Java are **covariant and reified**: an array remembers its element type at runtime and throws `ArrayStoreException` if you store the wrong type. Generics are **invariant and erased**. Mixing them would defeat the array store check, so the language bans creating arrays whose element type involves a type parameter or argument. ```java // T[] arr = new T[10]; // compile error: generic array creation // List<String>[] a = new List<String>[10]; // compile error ``` **Workaround:** create via reflection with a runtime `Class`, or create an `Object[]`/raw array and cast (with a localized `@SuppressWarnings("unchecked")`): ```java @SuppressWarnings("unchecked") T[] arr = (T[]) Array.newInstance(componentType, n); // componentType is a Class<T> you were given ``` ## 2. No `T.class` and limited `instanceof` `T.class` is illegal because there is no single runtime class for `T`. `instanceof List<String>` is illegal because the check could only ever verify `List`; only the *unbounded wildcard* form is allowed: ```java if (obj instanceof List<?>) { ... } // OK // if (obj instanceof List<String>) {} // compile error ``` **Workaround:** carry a `Class<T>` token and use `token.isInstance(obj)` / `token.cast(obj)`. ## 3. Same-erasure overload clash Two methods are distinct only if their *erased* signatures differ. These collide: ```java void m(List<String> s) {} void m(List<Integer> i) {} // compile error: same erasure m(List) ``` **Workaround:** rename, or differentiate by a non-generic parameter. ## 4. No type-parameter static members A type parameter belongs to an *instance* of the generic class, but `static` members are shared across all instantiations, so `static T field;` is illegal. ## 5. Unchecked casts and warnings Because the runtime cannot verify a cast to a parameterized type, casts like `(List<String>) obj` produce an **unchecked** warning — the JVM only checks `List`. Confine such casts to small, well-reviewed spots and annotate `@SuppressWarnings("unchecked")` narrowly. ## 6. Heap pollution Erasure makes it possible for a variable of a parameterized type to point to an object that violates that type, usually via raw types or unchecked casts: ```java List<String> ls = new ArrayList<>(); List raw = ls; // raw type raw.add(42); // no check at this point — heap polluted String s = ls.get(0); // ClassCastException only HERE ``` The failure is *deferred* to the read site because the `add` happened through an erased view. Varargs of generic types can also cause this — hence `@SafeVarargs`. ## 7. Catching a generic exception You cannot `catch (T e)` because exception matching is a runtime type check, which needs the (erased) concrete type. A generic class also cannot extend `Throwable`. ## The recurring fix: pass the type explicitly Nearly every workaround is the same move — *reintroduce the type as a runtime value*: a `Class<T>` token for single types (`enumMap`, `EnumSet.noneOf(Class)`, `Collections.checkedList`), or a **super type token** for full generic types. This is the engineering counterpart to the reflection topic: since the type isn't on the object, you must hand it in.
- Why are arrays of parameterized types disallowed when generic collections are fine?Arrays are reified and covariant — they runtime-check element stores (ArrayStoreException). Generics are erased and invariant. A List<String>[] would have an erased element type at the store check, so the array's own safety guarantee couldn't be enforced; the language bans it to avoid silent type holes.
- What does @SafeVarargs assert, and when is it appropriate?It suppresses the unchecked/heap-pollution warning on a varargs method whose parameter type is generic, asserting the method does not store anything unsafe into, nor leak, the implicitly-created generic array. Use it only on static/final/private methods you've verified never write a wrong-typed element into the varargs array or expose it.
saying these in an interview costs you the question
- Thinking new T[] compiles, or that the warning is harmless
- Using instanceof List<String> and expecting it to check the element type
- Believing two overloads differing only by type argument are distinct
- Treating an unchecked-cast warning as noise to blanket-suppress
- Not knowing heap pollution defers ClassCastException to the read site