92. Covariant Array Trap
Java arrays are covariant (String[] is an Object[]), meaning type-unsafe assignments compile but throw ArrayStoreException at runtime; generics are invariant and catch these errors at compile time.
Explanation
Array covariance: String[] strings = new String[3] can be assigned to Object[] objs = strings โ this compiles. But if you then objs[0] = Integer.valueOf(42), the JVM detects that the actual array type is String[] and throws ArrayStoreException. Every array write is guarded by a runtime type check โ performance overhead.
Generics are invariant: List<String> is NOT a List<Object>. List<Object> objs = (List<String>) strings is a compile error. This catches the bug at compile time. The cost: inflexibility when you want to pass a List<String> to a method that takes List<Object> โ you need bounded wildcards (List<? extends Object> or just List<?>).
The lesson: prefer List<T> over T[] in APIs to get compile-time type safety. Use arrays only in performance-critical low-level code or when required by an API.
Example
import java.util.ArrayList;
import java.util.List;
public class CovariantArrayTrap {
public static void main(String[] args) {
// Array covariance: compiles, but dangerous at runtime
String[] strings = new String[3];
Object[] objects = strings; // OK at compile time (covariance)
objects[0] = "hello"; // OK โ String is compatible
try {
objects[1] = 42; // ArrayStoreException! actual type is String[]
} catch (ArrayStoreException e) {
System.out.println("ArrayStoreException caught: " + e.getMessage());
}
// This can hide bugs: method accepts Object[], writes wrong type
static_fillArray(new String[3]); // passes Object[] โ trap!
// Generics are invariant: compile-time safety
List<String> stringList = new ArrayList<>();
// List<Object> objectList = stringList; // COMPILE ERROR โ invariant!
List<?> wildcard = stringList; // OK โ unbounded wildcard (read-only)
System.out.println("Wildcard size: " + wildcard.size());
// PECS: correct way to accept any List
List<String> names = List.of("Alice", "Bob");
printAll(names); // OK with List<? extends Object>
}
static void static_fillArray(Object[] arr) {
arr[0] = "string"; // OK
arr[1] = 42; // ArrayStoreException if arr is String[]!
}
// Correct: generic method is compile-time safe
static void printAll(List<?> items) {
items.forEach(System.out::println);
}
}Sources
- https://docs.oracle.com/javase/specs/jls/se21/html/jls-10.html#jls-10.5 โ JLS: array store exceptions
- https://docs.oracle.com/javase/tutorial/java/generics/why.html โ Why use generics?