Java 4: Arrays, Varargs & Defensive Copies
easy⏱ 5 mincoursejava
Covariant + reified arrays
String[] is a subtype of Object[] — array covariance. That means Object[] arr = new String[3]; arr[0] = 42; compiles but throws ArrayStoreException at runtime. Arrays carry their component type; the JVM checks every store. Generics are the opposite: List<String> is not a subtype of List<Object> (invariant), but the type info is erased at runtime.
Object[] arr = new String[3];
arr[0] = "hello"; // OK
arr[1] = 42; // throws ArrayStoreException at runtime
// Generics are invariant — this won't even compile:
// List<Object> list = new ArrayList<String>(); // error
Varargs are arrays
void log(String... messages) is exactly void log(String[] messages) at the bytecode level. The compiler wraps call-site arguments into an array. This means (1) passing no args creates an empty array (not null), (2) you can pass an existing String[] directly, (3) autoboxing with Integer... works but is slow for hot paths.
static int sum(int... nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
sum(); // sum(new int[0])
sum(1, 2, 3); // sum(new int[]{1, 2, 3})
int[] arr = {1, 2, 3};
sum(arr); // passes arr directly
Build a defensive-copy wrapper
A class exposing int[] getValues() can be sabotaged — the caller mutates the returned array. Write an ImmutableInts class that takes an array in the constructor, makes a defensive copy, and getValues() returns a fresh copy every call. Show that mutating the input array before construction and mutating the returned array after never affect internal state.
Use List.of(...) or Arrays.asList(...) instead of arrays
In modern Java, prefer immutable collections over arrays: List.of(1, 2, 3) returns an unmodifiable list — no defensive copy needed, and generics work properly. Arrays are still required for primitives and for varargs interop, but for ordinary data exchange, collections are safer.
Quiz: ArrayStoreException
Why doesn't Object[] o = new Integer[3]; o[0] = "hi"; cause a compile error? Array covariance — the compiler accepts Integer[] as Object[] and can't statically tell that the underlying component type rejects String. The JVM catches it at runtime. Generics would have rejected this at compile time.