29

Here's a question, this first code listing compiles just fine (JDK 1.6 | JDK 1.7):

ArrayList<String> a = new ArrayList<String>();
String[] s = a.toArray(new String[0]);

However, if I declare the List reference as a raw type:

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

I get a compiler error saying the String[] is required but Object[] was found.

This means my compiler is interpreting the generic method as returning Object[] despite of receiving a String[] as its argument.

I doubled-checked the toArray(myArray) method signature:

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

Therefore it is a parameterized method whose type parameter <T> has no relation whatsoever with that of the List (i.e. <E>).

I have no idea how using a raw type here affects the evaluation of parameterized methods using independent type parameters.

  • Does anyone has any idea why this code does not compile?
  • Does anybody knows any reference where this behavior is documented?
amaidment
  • 6,942
  • 5
  • 52
  • 88
Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205
  • 1
    What if you use `(String[])a.toArray();` Since you're not using generics, casting is a must. – Hovercraft Full Of Eels Jun 13 '12 at 03:16
  • @HovercraftFullOfEels Thanks for the suggestion. I know I can cast it, but I am studying for the programmer certification and I just found out about this while playing with some generics code and I cannot explain it. Never had run into something like this before. So, I am more looking for an explanation of why it happens. – Edwin Dalorzo Jun 13 '12 at 03:19
  • 1
    Interesting find. Oddly enough, if you explicitly specify the generic parameter as Object (ArrayList) the error goes away. Which is odd since Object is supposed to be the default for omitted types. – Perception Jun 13 '12 at 03:22
  • 1
    Also interesting, if you parameterize with a wildcard, `ArrayList> a = new ArrayList();`, the error goes away. Additionally, the error goes away if `Integer` is used as the type: `ArrayList a = new ArrayList();`. – creemama Jun 13 '12 at 03:32
  • Possible duplicate: http://stackoverflow.com/questions/1073786. – creemama Jun 13 '12 at 03:41
  • @creemama Yes, it appears it was asked before, but the explanation has not been provided. In the previous question the answer was: "you forgot to specify the type parameter in List". But I want to know why not specifying the type parameter `E` affects the type paramete `T` in the parameterized method. – Edwin Dalorzo Jun 13 '12 at 03:44

5 Answers5

36

It's not exactly what you'd expect, but if you refer to a generic class in raw form, you lose the ability to use generics in any way for instance members. It's not restricted to generic methods either, check out this:

 public class MyContainer<T> {

     public List<String> strings() {
         return Arrays.asList("a", "b");
     }
 }

 MyContainer container = new MyContainer<Integer>();
 List<String> strings = container.strings(); //gives unchecked warning!

This is the relevant part of the JLS (4.8):

The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • +1 It appears then that the compiler takes a hint on the use of a raw type and disables any kind of generics from there on in the given reference. Hmm, interesting. I wonder if this is documented in the JLS. By chance do you happen to know? – Edwin Dalorzo Jun 13 '12 at 03:41
  • @Edwin: I've cited it in my answer, looks like Premraj found it too. – Mark Peters Jun 13 '12 at 03:46
  • @MarkPeters Awesome this is what I was looking for. b(^_^)d – Edwin Dalorzo Jun 13 '12 at 03:49
8

When you don't use generics compiler treats it as a raw type and hence every generic type becomes Object and so you cannot pass String[] because it needs Object[]
So here is the deal - If you use

List l = new ArrayList<String>();

You are using raw type and all its instance members are replaced by its erasure counterparts. In particular each parameterized type appearing in an instance method declaration is replaced with its raw counterpart. See JLS 4.8 for details.

Premraj
  • 7,802
  • 8
  • 45
  • 66
  • How can the compiler tell I am not using generics, the type parameter of the List has nothing to do with the type parameter of the generic method toArray? – Edwin Dalorzo Jun 13 '12 at 03:24
  • The compiler knows whether you declared 'a' to be ArrayList or ArrayList. It knows this by... well, looking at what you wrote... ;-) – Neil Coffey Jun 13 '12 at 03:27
  • On second thoughts I believe that saying that the compiler goes into 1.4 compile mode is rather inaccurate. I understand that you meant that it treats the reference as if it was a raw type (as with JDK 1.4 when no generics existed), but your statement could be interpreted as if you meant that the code is compiled as if I had passed a `target=1.4` to my compiler, as such generating 1.4-compatible bytecodes, which I don't think is the case. At any rate, you might like to clarify that before somebody else makes you pay the price with any downvotes ;-) – Edwin Dalorzo Jun 13 '12 at 12:48
  • Indeed, I was just trying to simplify the things for understanding.. I'll update the answer :) – Premraj Jun 13 '12 at 13:07
6

This is the closest description I found in the specification to describe this observed behavior:

The type of a constructor (§8.8), instance method (§8.8, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the erasure of its type in the generic declaration corresponding to C. The type of a static member of a raw type C is the same as its type in the generic declaration corresponding to C.

It is a compile-time error to pass actual type parameters to a non-static type member of a raw type that is not inherited from its superclasses or superinterfaces.

Based on above, and observed behavior, I think its safe to say that all generic parameter types are removed from a raw type. Of course, the use of raw types themselves is discouraged in non-legacy code:

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of genericity into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

Perception
  • 79,279
  • 19
  • 185
  • 195
1

It may be interesting that this behaviour can be "solved". Use two interfaces, a base non generic interface and a generic interface. Then the compiler knows that all functions of the base non generic interface are not generic and will treat them like this.

This behaviour is very annoying if using streams and function chaining and therefore I solve it like following.

Soution via interface inheritence:

public interface GenericInterface<X extends Y> extends BaseInterface
{
    X getValue();
}

public interface BaseInterface
{
    String getStringValue();
}

Now you can do following without warning or problems:

GenericInterface object = ...;
String stringValue = object.getStringValue();
prom85
  • 16,896
  • 17
  • 122
  • 242
0

There can be no type parameter's passed into the toArray() method since your ArrayList is a non-parameteterized list, it only knows that it holds Objects, that's it. a.toArray() will always return an Object[] array. Again, you must cast it to (String[]) (with all the dangers therein) if you want to specify that it holds the specific String type.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • I appreciate the answer, but the internal array of `ArrayList ` is always of type `Object[]` regardless of I using generics or not. That does not affect the parameterized method when I use a parameterized `ArrayList ` reference. However, the method in question is parameterized, expecting a `T[] ` as its return type. How come it is overlooked by the compiler if it has nothing to do with the parameterized type of the `ArrayList`? I still cannot see the relation between `T` and `E` such that the compiler behaves this way. – Edwin Dalorzo Jun 13 '12 at 03:39