Java generics in ArrayList.toArray()
JavaArraysGenericsJava Problem Overview
Say you have an arraylist defined as follows:
ArrayList<String> someData = new ArrayList<>();
Later on in your code, because of generics you can say this:
String someLine = someData.get(0);
And the compiler knows outright that it will be getting a string. Yay generics! However, this will fail:
String[] arrayOfData = someData.toArray();
toArray()
will always return an array of Objects, not of the generic that was defined. Why does the get(x)
method know what it is returning, but toArray()
defaults to Objects?
Java Solutions
Solution 1 - Java
If you look at the implementation of toArray(T[] a)
of ArrayList<E> class, it is like:
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
Problem with this method is that you need to pass array of the same generic type. Now consider if this method do not take any argument then the implementation would be something similar to:
public <T> T[] toArray() {
T[] t = new T[size]; // compilation error
return Arrays.copyOf(elementData, size, t.getClass());
}
But the problem here is that you can not create generic arrays in Java because compiler does not know exactly what T
represents. In other words creation of array of a non-reifiable type (JLS §4.7) is not allowed in Java.
Another important quote from Array Store Exception (JLS §10.5):
> If the component type of an array were not reifiable (§4.7), the Java Virtual Machine could not perform the store check described in the > preceding paragraph. This is why an array creation expression with a > non-reifiable element type is forbidden (§15.10.1).
That is why Java has provided overloaded version toArray(T[] a)
.
> I will override the toArray() method to tell it that it will return an > array of E.
So instead of overriding toArray()
, you should use toArray(T[] a)
.
Cannot Create Instances of Type Parameters from Java Doc might also be interesting for you.
Solution 2 - Java
Generic information is erased at runtime. JVM does not know whether your list is List<String>
or List<Integer>
(at runtime T
in List<T>
is resolved as Object
), so the only possible array type is Object[]
.
You can use toArray(T[] array)
though - in this case JVM can use the class of a given array, you can see it in the ArrayList
implementation:
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
Solution 3 - Java
If you look at the Javadoc for the List
interface, you'll notice a second form of toArray
: <T> T[] toArray(T[] a)
.
In fact, the Javadoc even gives an example of how to do exactly what you want to do:
String[] y = x.toArray(new String[0]);
Solution 4 - Java
> I can, and will use an iterator instead of making an array sometimes, but this just always seemed strange to me. Why does the get(x) method know what it is returning, but toArray() defaults to Objects? Its like half way into designing it they decided this wasn't needed here??
As the intention of the question seems to be not just about getting around using toArray()
with generics, rather also about understanding the design of the methods in the ArrayList
class, I would like to add:
ArrayList
is a generic class as it is declared like
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
which makes it possible to use Generic methods such as public E get(int index)
within the class.
But if a method such as toArray()
is not returning E
, rather E[]
then things start getting a bit tricky. It would not be possible to offer a signature such as public <E> E[] toArray()
because it is not possible to create generic arrays.
Creation of arrays happen at runtime and due to Type erasure, Java runtime has no specific information of the type represented by E
. The only workaround as of now is to pass the required type as a parameter to the method and hence the signature public <T> T[] toArray(T[] a)
where clients are forced to pass the required type.
But on the other hand, it works for public E get(int index)
because if you look at the implementation of the method, you would find that even though the method makes use of the same array of Object to return the element at the specified index, it is casted to E
E elementData(int index) {
return (E) elementData[index];
}
It is the Java compiler which at the compile time replaces E
with Object
Solution 5 - Java
The pertinent thing to note is that arrays in Java know their component type at runtime. String[]
and Integer[]
are different classes at runtime, and you can ask arrays for their component type at runtime. Therefore, a component type is needed at runtime (either by hard-coding a reifiable component type at compile time with new String[...]
, or using Array.newInstance()
and passing a class object) to create an array.
On the other hand, type arguments in generics do not exist at runtime. There is absolutely no difference at runtime between an ArrayList<String>
and a ArrayList<Integer>
. It is all just ArrayList
.
That's the fundamental reason why you can't just take a List<String>
and get a String[]
without passing in the component type separately somehow -- you would have to get component type information out of something that doesn't have component type information. Clearly, this is impossible.
Solution 6 - Java
The very first thing you have to understand is what ArrayList
own is just an array of Object
transient Object[] elementData;
When it comes to the reason why T[]
is fail, it because you can't get an array of generic type without a Class<T>
and this is because java's type erase( there is a more explanation and how to create one). And the array[]
on the heap knows its type dynamically and you can't cast int[]
to String[]
. The same reason, you can't cast Object[]
to T[]
.
int[] ints = new int[3];
String[] strings = (String[]) ints;//java: incompatible types: int[] cannot be converted to java.lang.String[]
public <T> T[] a() {
Object[] objects = new Object[3];
return (T[])objects;
}
//ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
Integer[] a = new LearnArray().<Integer>a();
But what you put into the array
is just a object which type is E
(which is checked by compiler), so you can just cast it to E
which is safe and correct.
return (E) elementData[index];
In short, you can't get what don't have by cast. You have just Object[]
, so toArray()
can just return Object[]
(otherwise, you have to give it a Class<T>
to make a new array with this type). You put E
in ArrayList<E>
, you can get a E
with get()
.
Solution 7 - Java
An array is of a different type than the type of the array. It's sort of StringArray class instead of String class.
Assuming, it would be possible, an Generic method toArray()
would look like
private <T> T[] toArray() {
T[] result = new T[length];
//populate
return result;
}
Now during compilation, the type T gets erased. How should the part new T[length]
be replaced? The generic type information is not available.
If you look at the source code of (for example) ArrayList
, you see the same. The toArray(T[] a)
method either fills the given array (if the size matches) or creates a new new array using the type of the parameter, which is the array-type of the Generic Type T.
Solution 8 - Java
It is possible to create a "generic" array of the given(known) type. Normally I use something like this in my code.
public static <T> T[] toArray(Class<T> type, ArrayList<T> arrList) {
if ((arrList == null) || (arrList.size() == 0)) return null;
Object arr = Array.newInstance(type, arrList.size());
for (int i=0; i < arrList.size(); i++) Array.set(arr, i, arrList.get(i));
return (T[])arr;
}