14

I don't understand the compiler error resulting from the following code. I define a generic interface, see Task, with two methods: U doSomething(String value) and List<Integer> getIDs(). The doSomething() method actually uses the generic type as the type of its return value, but doesn't seem to be causing problems. The getIDs() method returns a List, which is unrelated to the type of Task, but it is causing problems when using for..each statement to iterate over the return value. The following compiler error occurs.

error: incompatible types
    for (Integer value : task.getIDs()){
required: Integer
found:    Object

It seems that the type erasure on the interface is causing the compiler to forget the declared type on the second method, which is unrelated to the generic type. Or in other words why is the generic type on the interface affecting how the compiler understands the return value on the getIDs() method and specifically in the context of a for..each statement?

Apparently if I get reference to the list outside of the for..each there is no problem, but not directly.

public class InterfaceTest {
   public static void main(String[] args) {
      Task task = new MyTask();
      // no complaints about the type here     
      List<Integer> values = task.getIDs();

      // getting a compiler error for this line
      for (Integer value : task.getIDs()){

      }
   }
}


interface Task<U>{
   U doSomething(String value);
   List<Integer> getIDs();
}

The implementation of the interface isn't necessary to demonstrate the point, but I didn't want to leave the reference Task task = null; and have answer's telling me that's the problem.

class MyTask implements Task<Boolean>{

   @Override
   public Boolean doSomething(String value) {
      System.out.println(value);
      return false;
   }

   @Override
   public List<Integer> getIDs() {
      return Arrays.asList( 1, 2, 3, 4 );
   }
}
arshajii
  • 127,459
  • 24
  • 238
  • 287
Martin Woolstenhulme
  • 3,968
  • 4
  • 24
  • 25
  • What does it do with `return new int[]{1,2,3,4}`, or `return Arrays.asList( new Integer(1), new Integer(2), ...);`? (note, not hypothetically. try it, add the result to the question to show that boxing/unboxing is clearly doing something odd) – Mike 'Pomax' Kamermans Jun 19 '13 at 02:09
  • This is a very good question; the answer is probably hidden deep in the guts of the JLS. – arshajii Jun 19 '13 at 02:09
  • 1
    @arshajii: Did you change `U doSomething(String value)` from `U doSomething(U value)`? I think the posted code doesn't actually what the OP is doing. – Bhesh Gurung Jun 19 '13 at 02:18
  • 1
    My guess is that because you declared `Task task` without a generic type, it drops all generics from the class definition, including the `` on `getIDs`. Not sure whether this is a bug or gotcha/feature. – Kevin Jun 19 '13 at 02:28
  • I up-voted all the answers, because they've all contributed to my understanding of the question. The answer from Lone nebula is a potential work around to keep the compiler happy if I define my Task interface with generics. Since the question is why is this the case I've accepted the answer citing the spec JLS-4.8-210, but I think both answers referencing the spec are valuable. – Martin Woolstenhulme Jun 19 '13 at 16:53
  • Thanks to @arshajii for fixing my code, I had been testing the interface definition to see if there would be a difference if the generic type was a parameter on the method instead of the return type, but it had no effect and I forgot to update my final sample code. – Martin Woolstenhulme Jun 19 '13 at 16:58

3 Answers3

9

What is happening is when use use a class (or interface) with a generic parameter <T> but refer to and instance of the without <T> (ie. that raw type) the compiler erases all generic type information from the class. This is likely due to compatibility with pre-1.5 source code where you wouldn't be able to use generic type information at all.

Consider the situation where you are writing code and compiling on a Java 1.4 compiler. You want to use a library which makes use of generics. When you refer to a type from that library which has generic parameters as a raw type, the compiler enforces the use of no generic parameters.

EDIT:

The JLS-4.8-210 alludes to this when it mentions (credit: zhong-j-yu):

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.

This still feels like a gotcha, but it is likely for some reason.

Community
  • 1
  • 1
Craig
  • 1,390
  • 7
  • 12
  • http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.8-210 The type of ... instance method ... of a raw type ... is the raw type that ... – ZhongYu Jun 19 '13 at 02:39
  • I believe I have located the appropriate JLS spec., see my answer. – Kevin Jun 19 '13 at 02:49
4

The error seems to lie here:

Task task = new MyTask();

You have forgotten to add generics after Task. It should work if you change it to one of these:

Task<Boolean> task = new MyTask();
Task<?> task = new MyTask();
Lone nebula
  • 4,768
  • 2
  • 16
  • 16
4

If I am interpreting the Java Language Specification (§4.6. Type Erasure) correctly, this is a "gotcha" of the language:

Type erasure also maps the signature (§8.4.2) of a constructor or method to a signature that has no parameterized types or type variables. The erasure of a constructor or method signature s is a signature consisting of the same name as s and the erasures of all the formal parameter types given in s.

I believe that this states that if you declare a type (Task) that is declared with a generic parameter (Task<U>) without said generic parameter, all its functions also lose their generic types, whether they are related or not. Therefore, your task.getIDs() is interpreted by the compiler as returning a plain List, not a List<Integer>. The iterator for that, of course, produces Objects, not Integers, causing the compiler error you see.

The reason for this is likely backwards compatibility with code produced before Java 1.5, when generics were introduced.

Kevin
  • 53,822
  • 15
  • 101
  • 132