35

I was just looking at the method defined in the List interface: <T> T[] toArray(T[] a) , and I have a question. Why is it generic? Because of that fact, method is not complete type-safe. The following code fragment compiles but causes ArrayStoreException:

List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);

String[] stringArray = list.toArray(new String[]{});

It seems to me if toArray was not generic and took List type parameter, it would be better.

I have written toy example and it is ok witout generic:

package test;

import java.util.Arrays;

public class TestGenerics<E> {
    private Object[] elementData = new Object[10];
private int size = 0;

    public void add(E e) {
    elementData[size++] = e;
}

@SuppressWarnings("unchecked")
    //I took this code from ArrayList but it is not generic
public E[] toArray(E[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (E[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

    public static void main(String[] args) {

    TestGenerics<Integer> list = new TestGenerics<Integer>();
    list.add(1);
    list.add(2);
    list.add(3);
    //You don't have to do any casting
    Integer[] n = new Integer[10];
    n = list.toArray(n);
}
}

Is there any reason why it is declared that way?

midas
  • 1,488
  • 2
  • 16
  • 20
  • 1
    It's generic because of the parameter `a`. It serves as a *type token*, telling the method what type of array to create. It's up to you to pass the correct type there. Otherwise the only thing the method could return is an `Object[]`. – millimoose Mar 14 '13 at 23:35
  • 1
    @millimoose I think the question is why passing the correct type is left up to the user, as opposed to being internally determined by the List. – ApproachingDarknessFish Mar 14 '13 at 23:36
  • 4
    @ValekHalfHeart The usual answer: type erasure. The `List` instance has no idea what its item type is, so doing so automatically is impossible. – millimoose Mar 14 '13 at 23:37
  • @millimoose True, but why do none of the other list methods take an argument of this nature? – ApproachingDarknessFish Mar 14 '13 at 23:38
  • 1
    @ValekHalfHeart Because those methods don't create new arrays, they just return an object you added to the `List` beforehand. The compiler can statically prove the code is type safe for those. – millimoose Mar 14 '13 at 23:39
  • @millimoose So without passing the array the list has no way to create a generic array? – ApproachingDarknessFish Mar 14 '13 at 23:41
  • @ValekHalfHeart That's correct. You cannot create "generic" arrays, because arrays (as opposed to lists) do in fact "know" the item type at runtime. (This is mostly a legacy design decision to allow assigning a `String[]` to a `Object[]` but avoid being able to write not-type-safe code this way. The collections API then punted on this until the introduction of generics.) If you tried to do `new T[]` the compiler wouldn't know what to use as this runtime data. – millimoose Mar 14 '13 at 23:44
  • If toArray was not generic it still would know what type of array to create with the help of a.getClass(). Array passed into method is already exist – midas Mar 15 '13 at 00:08
  • @Midas I believe dasblinkenlight's answer gets at the real reason why the parameter and result type aren't taken from the class. – millimoose Mar 15 '13 at 00:16
  • @millimoose I suppose everithing would be ok with backward-compatible witout – midas Mar 15 '13 at 00:47
  • Possible duplicate of [Why Collection.toArray\(T\[\]\) doesn't take an E\[\] instead](http://stackoverflow.com/questions/12355941/why-collection-toarrayt-doesnt-take-an-e-instead). – Paul Bellora Mar 15 '13 at 12:24
  • There are two styles to convert a collection to an array: either using a pre-sized array (like c.toArray(new String[c.size()])) or using an empty array (like c.toArray(new String[0]). In older Java versions using pre-sized array was recommended, as the reflection call which is necessary to create an array of proper size was quite slow.OpenJDK 6 this call was intrinsified, making the performance of the empty array version the same and sometimes even better, compared to the pre-sized version. – pvrforpranavvr Sep 23 '18 at 16:32

8 Answers8

57

From the javadocs:

Like the toArray() method, this method acts as bridge between array-based and collection-based APIs. Further, this method allows precise control over the runtime type of the output array, and may, under certain circumstances, be used to save allocation costs.

This means that the programmer is in control over what type of array it should be.

For example, for your ArrayList<Integer> instead of an Integer[] array you might want a Number[] or Object[] array.

Furthermore, the method also checks the array that is passed in. If you pass in an array that has enough space for all elements, the the toArray method re-uses that array. This means:

Integer[] myArray = new Integer[myList.size()];
myList.toArray(myArray);

or

Integer[] myArray = myList.toArray(new Integer[myList.size()]);

has the same effect as

Integer[] myArray = myList.toArray(new Integer[0]);

Note, in older versions of Java the latter operation used reflection to check the array type and then dynamically construct an array of the right type. By passing in a correctly sized array in the first place, reflection did not have to be used to allocate a new array inside the toArray method. That is no longer the case, and both versions can be used interchangeably.

Dave Moten
  • 11,485
  • 2
  • 34
  • 43
beny23
  • 32,077
  • 3
  • 78
  • 83
  • It seems beny23's and newacct's answers together will bring good answer for my question. And surely we have the parameter T[] a because of backward compatibility – midas Mar 15 '13 at 09:31
  • 4
    It is not so obvious, that Collection.toArray(new T[size]) is faster. Refer to this performance testing that proves that Collection.toArray(new T[0]) is faster (at least on OpenJDK) https://shipilev.net/blog/2016/arrays-wisdom-ancients/#_conclusion – solomkinmv Jan 04 '18 at 11:41
  • 2
    There are two styles to convert a collection to an array: either using a pre-sized array (like c.toArray(new String[c.size()])) or using an empty array (like c.toArray(new String[0]). In older Java versions using pre-sized array was recommended, as the reflection call which is necessary to create an array of proper size was quite slow.OpenJDK 6 this call was intrinsified, making the performance of the empty array version the same and sometimes even better, compared to the pre-sized version. – pvrforpranavvr Sep 23 '18 at 16:34
8

It is declared generically so that you can write code such as

Integer[] intArray = list.toArray(new Integer[0]);

without casting the array coming back.

It is declared with the following annotation:

@SuppressWarnings("unchecked")

In other words, Java is trusting you to pass in an array parameter of the same type, so your error does not occur.

Paul Bellora
  • 51,514
  • 17
  • 127
  • 176
rgettman
  • 167,281
  • 27
  • 248
  • 326
  • 2
    Not even necessarily a dummy parameter. For a performance improvement you can use `toArray(new Integer[list.size()])` and avoid having to create a new array via reflection. – millimoose Mar 14 '13 at 23:38
  • That's correct, I missed the part about it using the parameter array if it's big enough. I'll remove the "dummy" part. – rgettman Mar 14 '13 at 23:40
  • If toArray was not generic it still would know what type of array to create. And you can still write code like above without casting – midas Mar 15 '13 at 00:04
  • @rgettman please take a look at the update of the questions (toy example) – midas Mar 15 '13 at 01:24
4

The reason why the method has this signature is because the toArray API predates generics: the method

 public Object[] toArray(Object[] a)

has been introduced as early as Java 1.2.

The corresponding generic that replaces Object with T has been introduced as a 100% backward-compatible option:

public <T> T[] toArray(T[] a)

Changing the signature to generic lets callers avoid the cast: prior to Java 5, callers needed to do this:

String[] arr = (String[])stringList.toArray(new String[stringList.size()]);

Now they can do the same call without a cast:

String[] arr = stringList.toArray(new String[stringList.size()]);

EDIT :

A more "modern" signature for the toArray method would be a pair of overloads:

public <T> T[] toArray(Class<T> elementType)
public <T> T[] toArray(Class<T> elementType, int count)

This would provide a more expressive, and equally versatile, alternative to the current method signature. There is an efficient implementation of this, too, with Array.newInstance(Class<T>,int) method in place. Changing the signature in this way would not be backward-compatible, though.

Sergey Kalinichenko
  • 675,664
  • 71
  • 998
  • 1,399
  • But still I don't understand why toArray shoud be generic? I suppose it would be ok witout – midas Mar 15 '13 at 00:41
  • @Midas That's a good question: please take a look at the update of the answer. – Sergey Kalinichenko Mar 15 '13 at 00:52
  • 1
    This is wrong. The signature is not that way "because" of backwards compatibility. The signature is that way because it is the most reasonable and general signature for this method. – newacct Mar 15 '13 at 03:46
  • @newacct What you wrote is utterly wrong: an equally "reasonable and general signature for this method" would be the one using the new idiom not available prior to generics: `public T[] toArray(Class elementType)`. The only logical reason to stay with the the somewhat awkward current signature is backward compatibility. – Sergey Kalinichenko Mar 15 '13 at 07:06
  • @dasblinkenlight: but that method would not allow you to put elements into an existing array, which is the original method's functionality – newacct Mar 15 '13 at 08:58
  • 1
    @newacct The "existing array" functionality was merely a crutch that allowed the API designers send the array of a specific type into the `toArray` method. The fact that there is no overload taking an offset and length in a way similar to the `System.arraycopy` method is a good indication that the primary reason was passing the type, not the instance of the array (although it turned into a feature as soon as it got implemented). The reflection-based instantiation possibility was there, too, but it was too slow in Java 1.2. – Sergey Kalinichenko Mar 15 '13 at 09:32
3

It is type-safe -- it doesn't cause a ClassCastException. That's generally what type-safe means.

ArrayStoreException is different. If you include ArrayStoreException in "not type-safe", then all arrays in Java are not type-safe.

The code that you posted also produces ArrayStoreException. Just try:

TestGenerics<Object> list = new TestGenerics<Object>();
list.add(1);
String[] n = new String[10];
list.toArray(n); // ArrayStoreException

In fact, it is simply not possible to allow the user to pass in an array of the type they want to get, and at the same time not have ArrayStoreException. Because any method signature that accepts an array of some type also allows arrays of subtypes.

So since it is not possible to avoid ArrayStoreException, why not make it as generic as possible? So that the user can use an array of some unrelated type if they somehow know that all the elements will be instances of that type?

newacct
  • 110,405
  • 27
  • 152
  • 217
  • An alternative and equally as versatile signature for this method is `public T[] toArray(Class elementType)`. The type safety guarantee is still in place, and the users would be spared the manual creation of the array. Unfortunately, that signature was not available prior to generics, that's why a backward compatible one has been chosen. – Sergey Kalinichenko Mar 15 '13 at 06:54
2

The reason this method is as it is is mostly historic.

There is a difference between generic classes and array types: whereas the type parameters of generic class are erased at run-time, the type of the elements of arrays is not. So, at run-time, the JVM sees no difference between List<Integer> and List<String>, but it does see a difference between Integer[] and String[]! The reason for this difference is that arrays have always been there, from Java 1.0 onwards, whereas generics where only added (in a backward-compatible way) in Java 1.5.

The Collections API was added in Java 1.2, before the introduction of generics. At that time the List interface already contained a method

Object[] toArray(Object[] a);

(see this copy of the 1.2 JavaDoc). This was the only way to create an array with a user-specified runtime type: the parameter a served as a type token, that is, it determined the runtime type of the returned array (note that if A is a subclass of B, A[] is considered a subtype of B[] although List<A> is not a subtype of List<B>).

When generics were introduced in Java 1.5, many existing methods were made generic, and the toArray method became

<T> T[] toArray(T[] a);

which, after type erasure, has the same signature as the original non-generic method.

Hoopje
  • 11,820
  • 6
  • 29
  • 47
0

I think dasblinkenlight is probably correct that this has something to do with generifying an existing method, and full compatibility is a subtle thing to achieve.

beny23's point is also very good - the method should accept supertypes of E[]. One might attempt

    <T super E> T[] toArray(T[] a) 

but Java doesn't allow super on a type variable, for lack of use cases :)

(edit: nope, this is not a good use case for super, see https://stackoverflow.com/a/2800425/2158288)

Community
  • 1
  • 1
ZhongYu
  • 18,232
  • 5
  • 28
  • 55
  • Yes, it looks like T[] toArray(T[] a) will be perfect declaration. And you could not write like newcat's example. But Java doesn't allow super on a type variable. By the way why? Only because of lack of use cases? – midas Mar 15 '13 at 09:13
  • I think there would be no problem to allow it. Internally javac do have both upper bounds and lower bounds on type variables, so it's fine to allow programmer to declare lower bound (i.e. super LowerBound). Also Scala allows lower bound. – ZhongYu Mar 15 '13 at 16:04
  • Though not as useful as upper bounds, we do see valid use cases that call for lower bounds. Probably still not enough to justify to add the feature. – ZhongYu Mar 15 '13 at 16:05
0

This code is brutally copied from java.util.LinkedList class's source code:

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.newInstance(
                            a.getClass().getComponentType(), size)
     // actual code for copying
     return a;
}

This should dl the trick

Marco Ottina
  • 206
  • 3
  • 8
0

Finally with Java 11 we have a good compromise:

// Class Collection

default <T> T[] toArray(IntFunction<T[]> generator) {
    return toArray(generator.apply(0));
}

So if you provide array constructor it instantiate and copy data from collection to primitive array:

Set.of("a", "b").toArray(String[]::new);
List.of(1L, 2L).toArray(Long[]::new);
gavenkoa
  • 37,355
  • 13
  • 206
  • 248