20

Before Java generics, Collection.toArray() had no way to know which type of array the developer expected (particularly for an empty collection). As I understand it, this was the main rationale behind the idiom collection.toArray(new E[0]).

With generics, Collection<E>.toArray() can only return an array full of instances of E and/or its specialisations. I wonder why the return type still is as Object[] rather than E[]. In my opinion, returning an E[] instead of Object[] should not break existing code.

See: Collection.toArray(), Collection.toArray(T[]) and the related topic java: (String[])List.toArray() gives ClassCastException

Community
  • 1
  • 1

3 Answers3

9

It is a very good question. The answer is that generics are also called "erasures." It is not just a name. The information coded by generics is used at compile time only and then is removed. So, JVM even does not know this generic type E, so it cannot create array E[].

Other method toArray(T[] a) receives the information about the type from the argument at runtime. This is the reason this method's prototype is <T> T[] toArray(T[] a): it gets array of type T and can return array of type T. The type is passed as a parameter.

AlexR
  • 109,181
  • 14
  • 116
  • 194
  • Why does the same then not apply to Collection.iterator()? http://download.oracle.com/javase/6/docs/api/java/util/Collection.html#iterator%28%29 ? – Bernhard Bodenstorfer May 30 '11 at 07:49
  • Apparently, generic array creation is the problem. In my generic class' code, the following raises a compiler error “generic array creation”: final E[] returnArray = events.toArray( new E[events.size()] ); – Bernhard Bodenstorfer May 30 '11 at 08:07
  • @Bernhard, how could you create `new E[0]` ? What would that mean to the JVM at run time? Neither the `Collection` nor the `toArray()` method have any information about `E` at runtime – Lukas Eder May 30 '11 at 09:06
  • @Bernhard, an iterator does not need to know the type of the objects it is returning. By contrast, toArray() needs to actually create an array of the appropriate runtime type. – Old Pro May 18 '12 at 02:16
5

"Type erasure" is only a partial explanation: Neither the Collection, nor its toArray() method have any information about E at run time.

It is also because of backwards compatibility, that Collection.toArray() must still return Object[]. Before Java 1.5, there was no way of knowing a generic type for a collection, so this was the only reasonable API design.

Lukas Eder
  • 181,694
  • 112
  • 597
  • 1,319
  • I agree with you, Lukas. Moreover I wanted to start from backwords compatibility issue but than decided that the erasure is more important. Really, erasure does not even allow you to return typed array. So you cannot do this even if your create java today from scratch including generics and do not have any compatibility problems at all. – AlexR May 30 '11 at 08:47
  • You're right. But I didn't want to repeat your answer :-). Check also `Collection.contains(Object)` or `Collection.remove(Object)` which were left untouched for compatibility reasons. – Lukas Eder May 30 '11 at 09:04
  • 1
    No they were not. Those are like that because objects of different classes can be equal. – newacct May 31 '11 at 07:09
1

@Lukas, regarding: “new E[]”

The new E[0] raised the comiler error, as you probably expected. The workaround I have found is:

final E[] returnArray = (E[]) events.toArray( new Event[ events.size() ] );

N.B. the code is in a template class Listener<E extends Event>.

In my workaround, type erasure is both the problem and the solution. The cast to (E[]) is safe because its precise type is erased to Event[]. The only downside I see is the compiler warning about “unchecked or unsafe operations” (which, obviously, the cast is not in this case given type erasure).

@Lukas, regarding backward compatibility

I do not see a big problem with backward compatibility. Making the return type more special is not the same as making the argument type more special.

In other words, source code which so far expected Collection.toArray() to return an Object[] should be perfectly happy to receive an E[] instead.

And as to byte code, the Object[] and E[] are anyway the same due to type erasure.

  • some of the above would be better expressed as comments under the given answers. `new E[0]` should work. As far as type erasure, the compiler erases the generic type information at compile time (see http://stackoverflow.com/questions/339699/java-generics-type-erasure-when-and-what-happens). So the `toArray()` method doesn't have access to the type information at runtime, and thus can't make a run-time guarantee more specific than `Object[]`. `toArray(T[])`, on the other hand, gets the type passed in at run-time, and consequently can make the guarantee. – Paul W May 31 '11 at 02:12
  • Regarding backwards compatibility, consider the following code: `Collection collection = (whatever); Object[] r = collection.toArray(); r[0] = new Object();` which is perfectly acceptable under the original system, but would cause a runtime exception if `toArray` were modified to return `String[]`. (n.b. the runtime type of arrays is *not* erased, only of generic types!) – Jules Nov 19 '13 at 02:52
  • Also, the compiler warning you dismiss as irrelevant is not. Your array is not an E[], it is an Event[]. This means once you return it, if the reference is copied to two different sections of code, one of them could do this: `((Event[])returnedArray)[0] = new F();` (where `F` is a class that extends `Event` but is not `E`), which would cause the type guarantees of the generic code in the other section (which assumes all values in the array are `E` instances) to fail. If the array really were of type `E[]`, an appropriate exception would be raised when the store of an `F` was attempted. – Jules Nov 19 '13 at 03:00