Why does Java reject `new T[]` and `new List<String>[]`, and how do you create such arrays safely?
answer
- Arrays = reified + covariant; generics = erased
- Runtime store check only sees erased type -> heap pollution
- ArrayStoreException is the array safety net it would lose
- (T[]) new Object[n] -> unchecked warning, keep private (ArrayList trick)
- Array.newInstance(Class<T>, n) for a real typed array
basics
~20 sArrays in Java remember their element type at runtime and check every store against it, but generic types are erased and lost at runtime. Allowing generic arrays would let bad values slip in undetected, so the compiler forbids new T[] and new List<String>[]. The common fix is to create an Object[] (or use Array.newInstance with a Class token) and cast.
solid answer
~50 sIt's a clash between two features. Java arrays are *covariant and reified*: an array knows its true element type at runtime and throws `ArrayStoreException` on an illegal store. Generics are *erased*: the type argument is gone at runtime. If `new List<String>[]` were allowed, the array's runtime check could only verify `List`, not `List<String>`, so a `List<Integer>` could be stored and later read out as a `List<String>`, causing a `ClassCastException` far from the bug — defeating the whole point of generics. So the language bans *generic array creation*. Workarounds: create an `Object[]` and cast to `T[]` (you'll get an unchecked-cast warning, which you suppress and contain — this is what `ArrayList` does internally); or take a `Class<T>` token and call `Array.newInstance(componentType, length)` to build a properly-typed array reflectively. Prefer collections (`List<T>`) over arrays of generics whenever possible.
code
java · 21 lines// Compile error — generic array creation:
// List<String>[] bad = new List<String>[10];
// Why it's banned (the would-be hole):
// Object[] objs = bad; // array covariance
// objs[0] = List.of(42); // runtime check only sees 'List' -> sneaks in
// String s = bad[0].get(0); // ClassCastException far away
// Workaround A: encapsulated Object[] cast (ArrayList's approach)
class Stack<T> {
private T[] elements;
@SuppressWarnings("unchecked")
Stack(int cap) { elements = (T[]) new Object[cap]; } // kept private
}
// Workaround B: reified array via a Class<T> token
import java.lang.reflect.Array;
@SuppressWarnings("unchecked")
static <T> T[] newArray(Class<T> type, int len) {
return (T[]) Array.newInstance(type, len); // genuine T[]
}go deeper
Knows you can't make an array of a generic type directly and that ArrayList stores things in an Object[] under the hood.
Can state that arrays keep their type at runtime while generics are erased, and can write the (T[]) new Object[n] workaround with a suppress-warning.
Explains the covariance + reification vs erasure clash, walks through the heap-pollution scenario step by step, knows Array.newInstance and when to expose vs encapsulate the array.
Connects it to API design (toArray(T[]), @SafeVarargs pitfalls), the soundness trade-offs the language made, and prefers list-based designs while justifying when a reified array is warranted.
## Two features that don't mix To see why generic arrays are forbidden, you must understand how **arrays** and **generics** each treat type information at runtime — and that they are opposites. ### Arrays are *reified* and *covariant* - *Reified* means the type survives to runtime: a Java array object carries its real **component type** (element type) at runtime. `new String[3]` is, at runtime, genuinely a "String array". - *Covariant* means `String[]` is a subtype of `Object[]`. So this compiles: ```java Object[] arr = new String[3]; // legal — array covariance arr[0] = 42; // throws ArrayStoreException at RUNTIME ``` Because the array knows it's really a `String[]`, the JVM checks every write and throws `ArrayStoreException` if you try to store the wrong type. The array's runtime type check is the safety net. ### Generics are *erased* (non-reified) As covered for `new T()`: generic type arguments are a compile-time-only fiction. At runtime a `List<String>` and a `List<Integer>` are both just `List`. The `<String>` part is **erased** — not reified. ## Why `new T[]` and `new List<String>[]` are banned An array's safety depends on it knowing its real element type at runtime so it can run the store check. But a generic element type is erased, so the array could only check the *erased* type. Consider the forbidden code, with what would happen if it were allowed: ```java List<String>[] a = new List<String>[1]; // ILLEGAL (generic array creation) Object[] objs = a; // array covariance — legal List<Integer> li = List.of(42); objs[0] = li; // runtime check only sees 'List' -> PASSES! String s = a[0].get(0); // ClassCastException, far from the real bug ``` The array's runtime store check can only verify the *erased* type `List` — it cannot tell `List<String>` from `List<Integer>`. So a wrong-typed list slips in silently and blows up much later when read. To prevent this *heap pollution* (a generic variable referring to an object that isn't of its declared type), the compiler **forbids generic array creation** entirely: `new T[...]`, `new List<String>[...]`, `new T[]{...}` are all compile errors. (`new List<?>[]` with an *unbounded* wildcard is allowed, because `?` is reifiable enough — but you rarely want it.) ## Workaround 1: create `Object[]` and cast (the `ArrayList` trick) ```java class Stack<T> { private T[] elements; @SuppressWarnings("unchecked") Stack(int capacity) { elements = (T[]) new Object[capacity]; // unchecked cast warning } } ``` You allocate a real `Object[]` and cast it to `T[]`. The compiler emits an **unchecked-cast warning** because it can't guarantee the cast is sound. This is acceptable *only* when the array is private/encapsulated and you never expose it as a real `T[]` to the outside (otherwise a caller could trigger `ClassCastException`). This is exactly how `ArrayList`, `ArrayDeque`, etc. are implemented. Suppress the warning narrowly with `@SuppressWarnings("unchecked")` and add a comment explaining why it's safe. ## Workaround 2: `Array.newInstance` with a `Class<T>` token When you need an array whose runtime component type is *actually* `T` (e.g. to return a real `T[]`), pass a type token and build it reflectively: ```java import java.lang.reflect.Array; @SuppressWarnings("unchecked") static <T> T[] newArray(Class<T> componentType, int length) { return (T[]) Array.newInstance(componentType, length); } String[] arr = newArray(String.class, 5); // a genuine String[] ``` This produces an array with the correct reified component type, so it *can* be safely exposed (this is how `Arrays.copyOf` / `toArray(T[])` work). The cast warning remains but is genuinely safe here. ## Practical guidance - Prefer `List<T>` (and other collections) over arrays of generics — they coexist cleanly with erasure and give compile-time checks. *Effective Java* Item 28: "prefer lists to arrays." - If you must hold a generic array internally, use the `Object[]`-cast trick and keep it private. - If you must hand back a real typed array, use `Array.newInstance` with a `Class<T>`. - Beware also of `@SafeVarargs`: a generic varargs parameter (`T... args`) secretly creates a generic array and is a common heap-pollution source. ## Key takeaway Arrays need their real element type at runtime to stay safe; generics throw that type away. Letting them combine would silently poison the heap, so the compiler bans generic array creation. Recover a typed array via an `Object[]` cast (encapsulated) or `Array.newInstance` (reified), or just use a `List`.
- What is heap pollution and how does generic array creation cause it?Heap pollution is when a variable of a parameterized type refers to an object that isn't of that type. A generic array's runtime store check can only verify the erased type, so a wrong-typed element gets stored undetected, polluting the heap until a later read fails with ClassCastException.
- Is `new List<?>[10]` legal? Why?Yes — an array of an unbounded wildcard type is reifiable enough to allow, because <?> carries no specific type argument to lose. Arrays of concrete parameterized types like List<String>[] remain illegal.
saying these in an interview costs you the question
- Saying arrays and generics behave the same way at runtime
- Claiming new List<String>[] throws at runtime (it's a compile error)
- Exposing a (T[]) new Object[] cast array publicly as T[]
- Confusing ArrayStoreException with ClassCastException as the danger being prevented
- Forgetting that unbounded-wildcard arrays (List<?>[]) are actually allowed