Why are Java generics invariant when arrays are covariant, and what practical difference does that make?
answer
- Arrays covariant + reified; generics invariant + erased
- Invariance moves the error from runtime to compile time
- No runtime type info under erasure -> must be compile-time safe
- new T[] / new List<String>[] illegal because the two clash
- PECS: ? extends for read-covariance, ? super for write-contravariance
basics
~20 sGenerics are invariant: List<String> is not a List<Object>, so a bad store is caught at compile time. Arrays are covariant, so the same kind of bad store compiles and only fails at runtime with ArrayStoreException.
solid answer
~50 sArrays are covariant (String[] is a subtype of Object[]) but generics are invariant (List<String> is not a subtype of List<Object>). The difference is when type errors surface. With covariant arrays, assigning String[] to Object[] and then storing an Integer compiles fine and fails at runtime with ArrayStoreException — the type system is unsound and the JVM restores safety with a per-store check. Generics close that hole at compile time: because List<String> and List<Object> are unrelated, the unsafe add never compiles, so no runtime check is needed (and generics are erased, so there's no type info to check anyway). The practical consequences: mixing arrays and generics is awkward — you can't create generic arrays (new T[] is illegal) and arrays of parameterized types are disallowed. Effective Java's advice follows directly: prefer Lists to arrays. When you need covariant flexibility with generics, use bounded wildcards (? extends T for reading, ? super T for writing).
go deeper
Knows arrays and generics behave differently but may not articulate variance precisely.
States that generics are invariant and arrays covariant, and that the difference is compile-time vs runtime error detection.
Explains the soundness rationale, ties it to type erasure, knows generic-array creation is illegal, and applies PECS wildcards for safe variance.
Reasons about language-design tradeoffs (reification vs erasure, soundness, migration compatibility), and gives concrete API guidance (lists over arrays, wildcard variance, avoiding heap pollution) at scale.
## Two type relationships, two different choices **Variance** describes how subtyping of a container relates to subtyping of its element type, given `String` is a subtype of `Object`: - **Arrays are covariant:** `String[]` *is a subtype of* `Object[]`. - **Generics are invariant:** `List<String>` is *not* a subtype of `List<Object>` (nor the reverse). They are unrelated types. ## Why generics chose invariance Covariant arrays have an unsound hole: through an `Object[]` view you can store a non-matching element, which the compiler can't see. Java patches this at runtime with **ArrayStoreException** — a check on every reference-array store. Generics were designed (Java 5) to make such errors **compile-time** failures. If `List<String>` were a subtype of `List<Object>`, this would compile: ```java List<String> strings = new ArrayList<>(); List<Object> objects = strings; // imagine this were allowed... objects.add(42); // ...adding an Integer to a List<String>! String s = strings.get(0); // ClassCastException waiting to happen ``` To prevent it, the language makes the second line a **compile error**. Invariance is what makes generic collections statically type-safe. There's a second, mechanical reason: generics use **type erasure**. At runtime a `List<String>` is just a `List` — the element type is gone. So even if you wanted to do an ArrayStoreException-style runtime check for generics, *there's no type information left to check against*. Compile-time invariance is the only option. ## The practical fallout: arrays and generics don't mix Because arrays are covariant and reified (they carry their component type at runtime) while generics are invariant and erased, the two features clash: - **You cannot create a generic array:** `new T[10]` and `new List<String>[10]` are illegal. Allowing them would let covariant array semantics smuggle the wrong parameterized type past the (erased) type system. The workaround is `(T[]) new Object[10]` with an unchecked cast, or — better — use a `List`. - **Heap pollution warnings** appear where arrays and generics meet (e.g. varargs of generic type). This is the concrete basis for *Effective Java* Item "Prefer lists to arrays": lists give you compile-time safety; arrays give you a deferred runtime failure. ## Getting covariant-like flexibility safely: wildcards When you genuinely want a method to accept `List<String>` *or* `List<Integer>`, use **bounded wildcards** — and the PECS rule (Producer Extends, Consumer Super): ```java // reads only (producer) -> covariant via ? extends double sum(List<? extends Number> nums) { /* ... */ return 0; } // accepts List<Integer>, List<Double> // writes only (consumer) -> contravariant via ? super void addInts(List<? super Integer> sink) { sink.add(1); } ``` `? extends T` gives you safe covariance for reading (you can't add, so no unsafe store). `? super T` gives contravariance for writing. The compiler enforces these statically — no runtime check, no ArrayStoreException analogue. ## Summary table | | Arrays | Generics | |---|---|---| | Variance | Covariant | Invariant | | Element type at runtime | Reified (kept) | Erased (gone) | | Bad store detected | Runtime (ArrayStoreException) | Compile time | | Opt-in variance | none | wildcards (? extends / ? super) |
- Why is new List<String>[10] illegal?Arrays are covariant and reified; generics are erased. A generic array would let you assign it to an Object[], store a List<Integer> via the covariant view, and read it back as List<String> with no runtime check (the type is erased) — heap pollution. The language forbids creating generic arrays to prevent this.
- How do you accept both List<Integer> and List<Double> in one method parameter?Use a bounded wildcard: List<? extends Number>. This gives read-only (producer) covariance enforced at compile time, so you can iterate the numbers but cannot add an incompatible element.
saying these in an interview costs you the question
- Claiming generics are covariant or that List<String> is a List<Object>
- Saying you can freely create generic arrays like new List<String>[10]
- Confusing wildcards (compile-time variance) with array covariance (runtime-checked)
- Thinking the JVM does an ArrayStoreException-style check for generic collections