tags:

views:

1373

answers:

5

I came across something very basic but extremely bewildering today. I needed to convert a list to an array. The list contained String instances. Perfect example of using List.toArray(T[]), since I wanted a String[] instance. It would not work, however, without explicitly casting the result to String[].

As a test scenario, I used the following code:

import java.util.Arrays;
import java.util.List;

public class MainClass {
    public static void main(String args[]) {
        List l = Arrays.asList("a", "b", "c");
        String stuff[] = l.toArray(new String[0]);
        System.err.println(Arrays.asList(stuff));
    }
}

which does not compile. It's nearly an exact copy of the example in the javadoc, yet the compiler says the following:

MainClass.java:7: incompatible types
found   : java.lang.Object[]
required: java.lang.String[]
    String stuff[] = l.toArray(new String[0]);
                          ^

If I add a cast to String[] it will compile AND run perfectly. But that is not what I expect when I looked at the signature of the toArray method:

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

This tells me that I shouldn't have to cast. What is going on?

Edit:

Curiously, if I change the list declaration to:

List<?> l = Arrays.asList("a", "b", "c");

it also works. Or List<Object>. So it doesn't have to be a List<String> as was suggested. I am beginning to think that using the raw List type also changes how generic methods inside that class work.

Second edit:

I think I get it now. What Tom Hawtin wrote in a comment below seems to be the best explanation. If you use a generic type in the raw way, all generics information from that instance will be erased by the compiler.

+8  A: 

you forgot to specify the type parameter for your List:

List<String> l = Arrays.asList("a", "b", "c");

in this case you can write safety:

String[] a = l.toArray(new String[0]);

without any cast.

dfa
This makes sense to me logically, but not technically. It doesn't look to me that the toArray method cares what E is bound to (i.e., the element class of the list).
waxwing
the signature of toArray is "public <T> T[] toArray(T[] a)", T is the component type of the array passed in
dfa
I also can't understand it, worse is that it also works using a List<Object> (or List<?>) for "l"
Carlos Heuberger
The language specs is already rather complicated, so rather than come up with a load of even more complicated rules for partially genericised code (why would you partially genericise code?), all the generics get dropped. I believe JDK7 has -Xlint:rawtypes which would catch this error.
Tom Hawtin - tackline
Exactly. T is String[] in my example. Therefore it should return String[], no matter what elements are in the list.
waxwing
@Tom: I am not, I am perfectly happy with this as a solution of my problem, now I am just curious. :)
waxwing
@waxwing, the actual type of T[] is String[], so T is String, not T[]
dfa
This also compiles: String stuff[] = Arrays.asList(1, 2, 3).toArray(new String[0]);
Stephen Denne
(waxwing: Sorry, I was a little ambiguous. I didn't mean "you" as in you personally, but as in anyone.)
Tom Hawtin - tackline
T is ignored as we have chosen to ignore generics through the declaration of `l`. So T is like Object (the implicit bound).
Tom Hawtin - tackline
+1  A: 
List<String> l = Arrays.asList("a", "b", "c");

this will make it compile, you're using generics to say "this is a list of Strings" so the toArray method knows which type of array to return.

A: 

That's because your list contains objects, not strings. Had you declared your list as List<String>, the compiler would be happy.

JRL
+1  A: 

or do

List<?> l = Arrays.asList("a", "b", "c");  

still strange

Carlos Heuberger
why the downvote? doen't it compile?
Carlos Heuberger
+1 to counter. It does solve the problem - I noticed the same oddness myself.
waxwing
worst: it also compiles using List<Integer> but should crash at runtime... :-/
Carlos Heuberger
A: 

List without a declared type will default to "List of Object" while List<?> means "List of unknown". In the world of generic types it is a fact that "List of Object" is different from "List of String" but the compiler cannot say the same for "List of unknown". If you've declared it as unknown then, as far as the compiler can tell, that's OK.

The main point is that declaring something as the wildcard ? is different from declaring it as Object. Read more about wildcards here

henrik