Why does Java's Collection<E>.toArray() return an Object[] rather than an E[]?
Asked Answered
A

3

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

Allopatric answered 30/5, 2011 at 7:38 Comment(0)
G
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.

Goncourt answered 30/5, 2011 at 7:44 Comment(4)
Why does the same then not apply to Collection<E>.iterator()? download.oracle.com/javase/6/docs/api/java/util/… ?Allopatric
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()] );Allopatric
@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 runtimeSoares
@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.Sauder
S
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.

Soares answered 30/5, 2011 at 7:46 Comment(3)
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.Goncourt
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.Soares
No they were not. Those are like that because objects of different classes can be equal.Wareroom
A
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.

Allopatric answered 30/5, 2011 at 14:40 Comment(3)
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 #340199). 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.Warmhearted
Regarding backwards compatibility, consider the following code: Collection<String> 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!)Kuehn
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.Kuehn

© 2022 - 2024 — McMap. All rights reserved.