Java – “Potential heap pollution via varargs parameter” for Enum… why

enumsjavavariadic-functions

This question is specific to using varargs with generic-fied Enum<E>s:

Why do I get this warning Type safety: Potential heap pollution via varargs parameter elements if I define the method the like this:

<E extends Enum<E>> void someMethod(E... elements)

As opposed to this:

<E extends Enum<E>> void someMethod(E[] elements)

Accordingly, what should I look out for before declaring the method @SafeVarargs?

Similar questions

This question is similar to these questions regarding Collection<T>... but the scenarios shown in those answers do not seem to apply with Enum<E>...:

It is the reverse of this question which questions why there are no warning:

Sample code

This is what I tried to pollute the heap, but each bad attempt results in java.lang.ArrayStoreException and not a polluted array.

I am using Eclipse 4.6.0 and Java JDK 8u74.

public static void main(String[] args) {
    Foo[] x = { Foo.A };
    someMethod(x);

    Foo y = x[0];  // How does one get a ClassCastException here?
}

private static enum Foo {
    A, B, C,
}

private static enum Bar {
    X, Y, Z,
}

// This produces a "Type safety" warning on 'elements'
private static <E extends Enum<E>> void someMethod(E... elements) {
    Object[] objects = elements;

    // Test case 1: This line throws java.lang.ArrayStoreException
    objects[0] = "";

    // Test case 2: Program terminates without errors
    objects[0] = Foo.A;

    // Test case 3: This line throws java.lang.ArrayStoreException
    objects[0] = Bar.X;
}

Best Answer

There is a warning for the varargs method because varargs methods can cause implicit array creation at the calling site, whereas the version that takes an array parameter doesn't. The way that varargs works is that it causes the compiler to create an array at the call site filled with the variable arguments, which is then passed to the method as a single array parameter. The parameter would have type E[], so the array created should be an E[].

First, in the call site in your example, you are not using the variable arguments functionality at all. You are passing the array of variable arguments directly. So there is no implicit array creation in this case.

Even if you had used the variable arguments functionality, e.g. with someMethod(Foo.A);, the implicit array creation would be creating an array with a reifiable type, i.e. the variable argument type is known at the call site to be Foo which is a concrete type known at compile-time, so array creation is fine.

The problem is only if the variable argument type at the call site is also a generic type or type parameter. For example, something like:

public static <E extends Enum<E>> void foo(E obj) {
    someMethod(obj);
}

Then the compiler would need to create an array of this generic type or type parameter (it needs to create an E[]), but as you know generic array creation is not allowed in Java. It would instead create an array with the component type being the erasure of the generic type (in this example it would be Enum), so it would pass an array of the wrong type (in this example, the passed array should be an E[], but the array's actual runtime class would be Enum[] which is not a subtype of E[]).

This potential wrong type of array is not always a problem. Most of the time, a varargs method will simply iterate over the array and get elements out of it. In this case, the runtime class of the array is irrelevant; all that matters is that the elements are type E (which indeed they are). If this is the case, you can declare your method @SafeVarargs. However, if your method actually uses the runtime class of the passed array (for example, if you return the varargs array as type E[], or you use something like Arrays.copyOf() to create an array with the same runtime class), then a wrong runtime class of the array will lead to problems and you cannot use @SafeVarargs.

Your example is a bit weird because, well, you are not even using the fact that the elements are of type E, let alone the fact that the array is E[]. So you could use @SafeVarargs, and not only that, you could simply declare the array as taking Object[] in the first place:

private static void someMethod(Object... objects)
Related Topic