Compiler error on Java generic interface with a Li

2019-02-16 12:13发布

问题:

This question already has an answer here:

  • What is a raw type and why shouldn't we use it? 14 answers

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 );
   }
}

回答1:

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.



回答2:

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();


回答3:

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.