Back to results for “Arrays”
Java › Generics

How does variance (covariance, contravariance, invariance) apply differently to Java arrays versus Java generics, and what practical safety trade-off does this design difference represent?

Advanced

Answer

Java arrays are covariant (String[] is-a Object[]) yet enforce type safety only at RUNTIME (via ArrayStoreException on an invalid write); Java generics are, by default, invariant (List<String> is NOT a List<Object>, despite String being a subtype of Object) specifically BECAUSE this invariance lets the compiler catch type mismatches entirely at COMPILE time instead, avoiding array covariance's exact runtime-exception risk -- bounded wildcards (? extends T, ? super T) then let you deliberately opt INTO a form of controlled covariance/contravariance for generics on a case-by-case basis, giving you the flexibility of array-like variance when genuinely needed, but with the compiler still enforcing safety at compile time within that controlled scope (disallowing unsafe writes to a ? extends T reference, for instance) rather than deferring the check to a runtime exception the way arrays do.
// Arrays: covariant, but unsafe -- fails at RUNTIME
Object[] objArray = new String[3];
objArray[0] = 42;  // compiles, throws ArrayStoreException at runtime

// Generics: invariant by default, safe -- fails at COMPILE time instead
// List<Object> objList = new ArrayList<String>();  // COMPILE ERROR, caught immediately

// Generics with bounded wildcard: opt-in controlled covariance, STILL compile-time safe
List<? extends Object> objList2 = new ArrayList<String>();  // legal
// objList2.add("test");  // COMPILE ERROR -- can't write to a ? extends reference, still safe!
Real-world example A code review specifically flags a method signature using Object[] to accept different array element types (inheriting array covariance's runtime ArrayStoreException risk) and suggests refactoring to a generic method with a bounded wildcard instead, gaining equivalent flexibility while moving the safety check from a possible runtime exception back to a guaranteed compile-time error.

Common follow-ups: Why did the language designers choose different variance defaults for arrays versus generics, given both existed under the same type system?;What would Java generics look like if they had been designed to be covariant by default, similar to arrays?