Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to safely convert from a Collection of generic types to an array? [duplicate]

Tags:

java

generics

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()]);
like image 358
Paul Wagland Avatar asked May 19 '13 00:05

Paul Wagland


1 Answers

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.

like image 157
William Price Avatar answered Sep 30 '22 18:09

William Price