For various reasons I want to convert a list to an array, however the Collection contains objects that are themselves generics.
I have tried the following four options to get it to compile without needing a @supressWarnings('unchecked') annotation, but none of them work. Is there a solution to make this work correctly, or am I forced to use the annotation?
Iterator<T>[] iterators;
final Collection<Iterator<T>> initIterators = new ArrayList<Iterator<T>>();
// Type safety: Unchecked cast from Iterator[] to Iterator<T>[]
iterators = initIterators.<Iterator<T>>toArray(
(Iterator<T>[])new Iterator[initIterators.size()]);
// Type safety: Unchecked invocation toArray(Iterator[]) of the generic
// method toArray(T[]) of type Collection<Iterator<T>>
// Type safety: The expression of type Iterator[] needs unchecked conversion
// to conform to Iterator<T>[]
iterators = initIterators.<Iterator<T>>toArray(
new Iterator[initIterators.size()]);
// Type safety: The expression of type Iterator[] needs unchecked conversion
// to conform to Iterator<T>[]
iterators = initIterators.toArray(new Iterator[initIterators.size()]);
// Doesn't compile
iterators = initIterators.toArray(new Iterator<T>[initIterators.size()]);
There is no type-safe way to create an array of a parameterized type such as Iterator<T>[]
.
Alternatively, you can create a raw array: Iterator<?>[]
. Or, if you can avoid the use of arrays entirely, use a collection type like List<Iterator<T>>
.
The reason it is not possible is that Java arrays are covariant and the parameterized bounds of Generic types are invariant. That is to say:
Integer[] integers = new Integer[1];
Number[] numbers = integers; // OK because Integer extends Number
numbers[0] = new Double(3.14); // runtime exception
The compiler allows the assignment because Double
extends Number
and the declared type of numbers
is Number[]
. But at runtime the actual array object instance is the original Integer[1]
and arrays know the type of the objects they contain.
With generics, parameterized types are different. For one, due to compile-time type erasure they do not intrinsically know their runtime types.
List<Integer> integerList = new ArrayList<Integer>();
List<Number> numberList = integerList; // compiler error, prevents:
numberList.add(new Double(3.14)); // would insert a Double into integerList
Collection<Integer> integerCollection = integerList; // allowed
// OK because List extends Collection and the <type parameter> did not change
Collection<Number> numberCollection = integerList; // compiler error
// an "Integer" is a "Number"
// but "a collection of Integers" is more specific than "a collection of Numbers"
// and cannot be generally treated the same way and guarantee correct behavior
List<?> rawList = integerList; // allowed, but...
rawList.add(new Integer(42)); // compiler error, Integer is not a ... a what?
With generics, in Java, you are relying on the compiler (not the runtime) to validate that the generic types are correct and safe.
So while an Iterator<?>[]
knows at runtime that it is an array that contains Iterator
elements, the <T>
in Iterator<T>[]
is erased at compile time and the runtime has no way to know what it was supposed to be. So you get an unchecked warning.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With