Java generics in ArrayList.toArray()

JavaArraysGenerics

Java 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;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRabbit GuyView Question on Stackoverflow
Solution 1 - JavajustAbitView Answer on Stackoverflow
Solution 2 - JavaAdamSkywalkerView Answer on Stackoverflow
Solution 3 - JavaachView Answer on Stackoverflow
Solution 4 - JavasenseiwuView Answer on Stackoverflow
Solution 5 - JavanewacctView Answer on Stackoverflow
Solution 6 - JavaTonyView Answer on Stackoverflow
Solution 7 - JavaGerald MückeView Answer on Stackoverflow
Solution 8 - JavaAlexander SamoylovView Answer on Stackoverflow