33

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?

ThinkingStiff
  • 62,391
  • 29
  • 139
  • 237
justadreamer
  • 2,307
  • 2
  • 20
  • 24
  • 2
    Jave doesn't really have a concept of `T[]`. – Gabe Oct 26 '11 at 22:58
  • possible duplicate of [java: (String\[\])List.toArray() gives ClassCastException](http://stackoverflow.com/questions/5690351/java-stringlist-toarray-gives-classcastexception) – Daniel Pryden Oct 27 '11 at 16:28

5 Answers5

29

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.

Mike Samuel
  • 109,453
  • 27
  • 204
  • 234
  • thank you Mike, I didn't notice this method T[] toArray(T[] arg0) - it returns what I need. – justadreamer Oct 26 '11 at 22:38
  • 9
    or use `toArray(new String[list.size()])` if you want to avoid allocation of a size 0 array – ratchet freak Oct 26 '11 at 22:40
  • So the answer below says that the `List` 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? – Patrick M Jun 04 '15 at 17:30
  • 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. – Patrick M Jun 04 '15 at 17:38
16

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.

Daniel Pryden
  • 54,536
  • 12
  • 88
  • 131
  • 1
    "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. – newacct Oct 27 '11 at 04:31
  • 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 Oct 27 '11 at 11:13
  • @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](http://stackoverflow.com/questions/6173495/why-does-javas-collectione-toarray-return-an-object-rather-than-an-e) 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. – Daniel Pryden Oct 27 '11 at 16:26
  • @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 Pryden Oct 27 '11 at 16:33
  • @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. – newacct Oct 28 '11 at 03:49
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.

Costi Ciudatu
  • 33,403
  • 5
  • 52
  • 89
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()]);
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.

user207421
  • 289,834
  • 37
  • 266
  • 440
  • what would you change the signature to? – newacct Oct 27 '11 at 04:33
  • you mean return type T[]? you'll get a ClassCastException if you try to use it, because the method still creates an Object[] – newacct Oct 27 '11 at 09:54
  • @newacct when you change a method signature it is customary to change the implementation to match. However see my edit. – user207421 Oct 27 '11 at 21:20
  • 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 Oct 28 '11 at 03:32
  • @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(). – user207421 Oct 28 '11 at 04:10
  • All methods have been changed. Map.put() List.set() List.get() everything. Map.get() has nothing to do with backwards compatibility. This has been explained in many posts. It's signature is required to be that way because of what the method does. – newacct Oct 28 '11 at 04:26
  • @newacct You can't have it both ways. Either all methods have been changed or Map.get() hasn't been changed. – user207421 Oct 28 '11 at 04:33
  • As I already told you, it would be incorrect to change Map.get(), because it would violate its contract. This has nothing to do with generics. – newacct Oct 28 '11 at 05:22
  • @newacct and you also told me that all methods have been changed. There is a high level of internal contradiction here. – user207421 Oct 28 '11 at 05:38