40

Does anybody know why Java 1.6 has this behaviour:

List<String> list = ArrayList<String>();
String[] arr = (String[]) list.toArray();

And I get a ClassCastException, because it returns Object[] and not String[].

I thought List<T>.toArray() should return T[] - no? Does anyone have an answer why this inconvenience exists in the language? And also how to work around this? How do I get a String[] from List<String> without looping thru the items?

2

6 Answers 6

32

You need to pass in an array so its runtime type can be used as a hint by toArray. Try toArray(new String[0]) instead. You can also pass in a pre-sized array.

To understand, consider what type erasure would have to do to make

new T[4]

work. If Java allowed that, the best it could do post erasure is

new Object[4]

Most toArray implementations use java.lang.reflect.Array to construct an output array of the right type given a type hint passed as a Class.

Sign up to request clarification or add additional context in comments.

4 Comments

thank you Mike, I didn't notice this method T[] toArray(T[] arg0) - it returns what I need.
or use toArray(new String[list.size()]) if you want to avoid allocation of a size 0 array
So the answer below says that the List<E> interface can't provide an E[] toArray() method because it would conflict with the older Object[] toArray() method, which was introduced in 1.2 and required for backwards compatibility. This makes sense as you can't have two method signatures that vary only by the return type. But your answer implies that E[] toArray() wouldn't work because of type erasure - did I read that right? Or is there a nuance between you using T as your generic type and the List interface using E for its declaration?
Right, I think I missed the obvious. List methods like E get(int index) work because they're enforced at compile time and don't have to know or construct the type at run time. E[] toArray() could have its returned value assignment/pass/etc. checked at run time, but actually constructing the array requires knowledge of the correct type.
18

Because arrays have been in Java since the beginning, while generics were only introduced in Java 5. And the List.toArray() method was introduced in Java 1.2, before generics existed, and so it was specified to return Object[]. Lots of existing code now expects List.toArray() to return Object[], so it can't be changed now.

Furthermore, generics are erased at runtime, so ArrayList couldn't even construct an array of the proper type if it wanted to.

The loophole for this is the List.toArray(T[]) method, which will return you an array of the correct type, provided you give it an array of the correct type so it knows what type to use.

5 Comments

"and so it was specified to return Object[]. Lots of existing code now expects List.toArray() to return Object[], so it can't be changed now." Nonsense. Java collection methods' return types were changed for generics. This is completely irrelevant. The actual reason is that arrays of different types are different at runtime and thus it cannot possibly return the array of the proper type. This is true both before and after generics.
Daniel, seriously? This question has been asked on SO how many times? A million? And you were lecturing me on answering a duplicate question?
@irreputable: I didn't immediately see (nor offhand remember) an exact duplicate when I answered this yesterday. A quick search today turned up this one which I think is close enough. If you're convinced that a question is a duplicate, then rather than assuming bad faith, why not just vote to close yourself? You have enough rep.
@newacct: I'm not sure that it's completely irrelevant. True, Java 5 also introduced return type covariance, and so it wouldn't necessarily be a breaking change to return T[] instead of Object[]. But due to the weird way in which array variance works:, would it be correct to return T[]? My general rule of thumb is that you can use generics, or make use of array covariance, but should avoid trying to use both at the same time.
@Daniel: return type covariance (which deals with return types of overriding methods) is not at issue here, since the toArray() methods in all of its superclasses (Collection) would also be changed to T[]. As to your question about whether it would be correct, supposing that a method could magically return a T[], it would be correct in most cases, because T[] is a subclass of Object[], so anyone who expects an Object[] should be able to take a T[]. The only exception would be if they tried to then insert an element into the array of a different type, a highly unusual possibility.
4

To understand why that happens, just have a look at the javadoc for Collection.toArray() and then look at the Collection.toArray(T[] a) method. Also keep in mind that generics in Java are erased at runtime.

Comments

3

Since Java 11 it's even easier than String[] str = list.toArray(new String[0]) or String[] str = list.toArray(new String[list.size()]):

String[] str = list.toArray(String[]::new);

Comments

1

The reasons are two:

  1. The method preceded Generics and they couldn't change the signature without breaking backwards compatibility.

  2. In any case, as the List is constructed without a Class<T> there is no way for the implementation to construct an array of type T.

9 Comments

what would you change the signature to?
you mean return type T[]? you'll get a ClassCastException if you try to use it, because the method still creates an Object[]
@newacct when you change a method signature it is customary to change the implementation to match. However see my edit.
my objection is that all the method signatures in the Java api did change (e.g. all the collection classes' methods, etc.) for generics. so your claim that it can't change because it used to be that way is not true. plus T[] is a subclass of Object[], so it will not "break" any code that expects Object[] if they changed it to T[]
@newacct (a) if that's your objection, you've taken far too long to make it clear, i.e. 3 separate answers; (b) no Collections methods were changed in a backwards-incompatible way: consider for example Map.get().
|
1

Workaround will be to use:

       List<String> list = new ArrayList<>();
         list.add("Sputnik");
         list.add("Explorer");

        //String[] str = list.toArray(); 

This is bound to throw an : incompatible types: Object[] cannot be converted to String[] error

       Instead use:

        String[] str = list.toArray(new String[0]);
        or
        String[] str = list.toArray(new String[list.size()]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.