60

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?

Rabbit Guy
  • 1,779
  • 3
  • 15
  • 26
  • What are you talking about? Which class has the ``toArray()`` method? – f1sh Apr 13 '16 at 12:52
  • ArrayList has a `toArray()` method, but even when you define the generic, the `toArray()` method will return `Object[]`, not `E[]`, which is implied using the generic. – Rabbit Guy Apr 13 '16 at 12:53
  • 11
    Instead of overriding `toArray()` you can use `toArray(T[] a)` method to get appropriate array. – justAbit Apr 13 '16 at 12:54
  • 3
    Yes, I know I can to `toArray(T[] a)`, but why is it that it wasn't just built directly into `toArray()`. I don't understand why `get(x)` knows what it is giving out but `toArray()` does not. – Rabbit Guy Apr 13 '16 at 12:56
  • call `toArray(new String[0])` is sufficient and necessary to give the `toArray` the sufficient information to create the right kind of array. – njzk2 Apr 13 '16 at 14:05
  • Maybe worth to remember: When you want to create an instance of a dynamic type, i.e. not via `new`, you *always* need an instance of `Class`. If you already have an object of the desired type, you can use `obj.getClass()` to get the `Class` and create a new instance from it. However, you cannot get a `Class` instance from a generic type parameter, and so you cannot simply create a new instance of type `T` at runtime. - `toArray(T[] a)` could as well be provided as `toArray(Class t)` but that would be less efficient in some cases. – JimmyB Apr 13 '16 at 15:03
  • 1
    @JimmyB: an alternative is some kind of factory for the dynamic type, e.g. [Java 8’s `Stream.toArray`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#toArray-java.util.function.IntFunction-) uses `IntFunction` as factory type, passed as parameter. The reason this pattern hasn’t been used before, e.g. for the `Collection` interface, is that only Java 8 allows to implement it as neat as `ElementType[]::new`, allowing, e.g. `String[] array = stream.toArray(String[]::new)`… – Holger Apr 13 '16 at 15:45
  • @Holger Yes, but then the *factory* will have to either statically be bound to a class (compile time, `String[]::new`) or have access to a `Class` object at runtime. `T[]::new` still won't work. – JimmyB Apr 13 '16 at 15:53
  • @JimmyB: sure, it’s not contradicting to what you said. It’s just a practical alternative to passing a `Class` object or an existing instance of the desired type. In all cases, the object has to be provided by someone in the call chain where the desired type is reifiable. – Holger Apr 13 '16 at 15:56
  • I'd consider this as a duplicate of a chain of dulicates...: http://stackoverflow.com/questions/7909747/why-does-liststring-toarray-return-object-and-not-string-how-to-work-ar , http://stackoverflow.com/questions/5690351/java-stringlist-toarray-gives-classcastexception .... – Marco13 Apr 13 '16 at 21:56
  • Use JavaScript typed arrays instead. I am done with Java – Rajesh Jain Apr 13 '16 at 22:35

8 Answers8

60

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.

justAbit
  • 4,066
  • 1
  • 17
  • 32
  • Thank you. This explains it in black and white why it fails. – Rabbit Guy Apr 13 '16 at 13:11
  • In short, Java does not allow a method to be *polymorphic* (i.e. generic) in (only) its return value. An example of a language which *does* support this is Haskell. – jpaugh Apr 13 '16 at 21:28
  • 4
    I think you captured the crux of the problem which is simply that Java cannot create arrays of generic types. – Pace Apr 14 '16 at 01:49
  • 7
    @jpaugh Java does allow that, and it even infers the type of the RHS of an assignment based on the LHS. You can also explicitly declare the type parameters when invoking a generic method. See, for instance, http://ideone.com/AObtRR. – JohannesD Apr 14 '16 at 10:39
  • 1
    @JohannesD I stand corrected. (Thanks!) Examples like `toArray` and your link to `getnull` show that Java comes *so close*, and just misses the mark. Then again, Haskell does not even attempt subtyping, so its hard to compare their generics support one-for-one. – jpaugh Apr 14 '16 at 19:09
21

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());
AdamSkywalker
  • 10,723
  • 2
  • 33
  • 71
  • 5
    Your answer is correct but not easy to understand for non-Java experts. The important fact is that the returned array has to be created at runtime (-> missing info of what type), where as `get(..)` just returns an existing object. – Robert Apr 13 '16 at 12:58
16

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]);

ach
  • 5,764
  • 1
  • 22
  • 28
5

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

senseiwu
  • 4,069
  • 4
  • 19
  • 39
5

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.

newacct
  • 110,405
  • 27
  • 152
  • 217
1

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.

Gerald Mücke
  • 9,470
  • 1
  • 40
  • 62
1

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().

Community
  • 1
  • 1
Tony
  • 5,213
  • 1
  • 31
  • 47
0

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;
}
Alexander Samoylov
  • 1,290
  • 1
  • 15
  • 20