I am using IntelliJ IDEA with javac on JDK 1.8. I have the following code:
class Test
{
@SafeVarargs
final void varargsMe
None of the answers I've seen on this question seem to me to be satisfactory so I thought I'd take a stab at it.
Here's the way I see it:
@SafeVarargs
[unchecked] Possible heap pollution from parameterized vararg type Foo
.@SuppressWarnings("varargs")
[varargs] Varargs method could cause heap pollution from non-reifiable varargs parameter bar
.So if I take the following simple variation on OP's original code:
class Foo {
static <T> void bar(final T... barArgs) {
baz(barArgs);
}
static <T> void baz(final T[] bazArgs) { }
}
The output of $ javac -Xlint:all Foo.java
using the Java 9.0.1 compiler is:
Foo.java:2: warning: [unchecked] Possible heap pollution from parameterized vararg type T
static <T> void bar(final T... barArgs) {
^
where T is a type-variable:
T extends Object declared in method <T>bar(T...)
1 warning
I can make that warning go away by tagging bar()
as @SafeVarargs
. This both makes the warning go away and, by adding varargs safety to the method contract, makes sure that anyone who calls bar
will not have to suppress any varargs warnings.
However, it also makes the Java compiler look more carefully at the method code itself - I guess in order to verify the easy cases where bar()
might be violating the contract I just made with @SafeVarargs
. And it sees that bar()
invokes baz()
passing in barArgs
and figures since baz()
takes an Object[]
due to type erasure, baz()
could mess up the heap, thus causing bar()
to do it transitively.
So I need to also add @SuppressWarnings("varargs")
to bar()
to make that warning about bar()
's code go away.
Assuming that I know what I am doing
I refuse to assume this since this seems incredibly wrong.
The situation you're running into here is that, because generics are not reifiable, you're declaring a generic array, which is generally frowned upon. Generics and arrays don't mix very well, given that an array is covariant (String[]
is an Object[]
in the same way that a String
is an Object
), whereas generics are invariant (List<String>
is not a List<Object>
, even though a String
is an Object
).
If you want a collection of collections...just pass one. It's safer than mingling arrays and generics.
final void varargsMethod(Collection<<Collection<? super T>> collections) { }
An additional (and quite superfluous-looking) @SuppressWarnings( "varargs" )
is needed to suppress the warning, as follows:
@SafeVarargs
@SuppressWarnings( "varargs" )
final void varargsMethod( Collection<T>... varargs )
{
arrayMethod( varargs );
}
This might explain the reason why. I just copied it from Effective Java 2nd Edition, Item 25. Hopefully, it can help.
The prohibition on generic array creation can be annoying. It means, for example, that it’s not generally possible for a generic type to return an array of its element type (but see Item 29 for a partial solution). It also means that you can get confusing warnings when using varargs methods (Item 42) in combination with generic types. This is because every time you invoke a varargs method, an array is created to hold the varargs parameters. If the element type of this array is not reifiable, you get a warning. There is little you can do about these warnings other than to suppress them (Item 24), and to avoid mixing generics and varargs in your APIs.
In fact you should not write your code in this way. Consider the following example:
import java.util.*;
class Test<T extends Throwable>
{
@SafeVarargs
@SuppressWarnings("varargs")
final void varargsMethod( Collection<T>... varargs )
{
arrayMethod( varargs );
}
void arrayMethod( Collection<T>[] args )
{
Object[] array = args;
array[1] = new Integer(1);
//
//ArrayList<Integer> list = new ArrayList<>();
//list.add(new Integer(1));
//array[1] = list;
}
public static void main(String[] args)
{
ArrayList<Exception> list1 = new ArrayList<>();
ArrayList<Exception> list2 = new ArrayList<>();
(new Test<Exception>()).varargsMethod(list1, list2);
}
}
If you run the code, you will see an ArrayStoreException because you put an Integer into a Collection<T>
array.
However, if you replace array[1] = new Integer(1); with the three comment lines (i.e. to put an ArrayList<Integer>
into the array), due to type erasure, no exception is thrown and no compilation error occurs.
You want to have a Collection<Exception>
array, but now it contains a ArrayList<Integer>
. This is quite dangerous as you won't realise there is a problem.