Varargs, ambiguity and heap pollution
A convenience that hides an array, and generics make it unsafe.
Open this lesson in the learning hubKey points
- Varargs is an array at the call site. The compiler allocates one for every call, which is why a hot loop over a varargs method allocates more than it looks like it does.
- Passing an existing array works, but passing a
nullliteral is ambiguous: it can be the whole array or one element that happens to be null, and the compiler warns rather than choosing well. - Overload resolution prefers a non-varargs method. Adding an overload can therefore silently change which method an existing call binds to, without any error.
- Generic varargs create an array of a generic type, which the language cannot express soundly. That is heap pollution - the compiler warns and the failure appears later, elsewhere, as a ClassCastException.
@SafeVarargsasserts that the method only reads the array and never stores anything into it or lets it escape. It suppresses the warning; it does not verify the claim.- The classic trap:
Arrays.asList(intArray)produces a single-element list containing the array, becauseint[]is one object rather than an array of elements.
Example
import java.util.Arrays;
import java.util.List;
public class VarargsTraps {
static String describe(Object... args) {
return args == null ? "null array" : "array of " + args.length;
}
// Overload resolution prefers the SPECIFIC method over varargs.
static String pick(int a, int b) { return "two ints"; }
static String pick(int... values) { return "varargs of " + values.length; }
// Generic varargs: the array type cannot be expressed soundly.
@SafeVarargs
static <T> List<T> listOf(T... items) {
return Arrays.asList(items); // safe: we only READ the array
}
// NOT safe - it lets the array escape, where it can be corrupted.
static <T> T[] leak(T... items) { return items; }
public static void main(String[] args) {
System.out.println(describe("a", "b"));
System.out.println(describe());
System.out.println(describe((Object[]) null)); // the whole array is null
System.out.println(describe((Object) null)); // ONE null element
System.out.println();
System.out.println("pick(1, 2) -> " + pick(1, 2));
System.out.println("pick(1, 2, 3) -> " + pick(1, 2, 3));
// The Arrays.asList trap: int[] is ONE object, not many elements.
int[] primitives = {1, 2, 3};
List<int[]> wrapped = Arrays.asList(primitives);
System.out.println();
System.out.println("asList(int[]).size() = " + wrapped.size());
Integer[] boxed = {1, 2, 3};
System.out.println("asList(Integer[]).size() = " + Arrays.asList(boxed).size());
// HEAP POLLUTION - the failure lands far from the cause.
String[] leaked = leak("a", "b");
Object[] asObjects = leaked; // legal: arrays are covariant
try {
asObjects[0] = 42; // compiles, fails at runtime
} catch (ArrayStoreException e) {
System.out.println();
System.out.println("ArrayStoreException: " + e.getMessage()
+ " <- array covariance caught it here");
}
}
}
Varargs is an array, generic varargs are unsound, and @SafeVarargs asserts safety rather than checking it.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Core Java course, and every lesson in it is listed on the Core Java contents page.