Java泛型接口中带有List<>方法的编译器错误

14
我不理解以下代码导致的编译错误。我定义了一个泛型接口Task,其中包含两个方法:U doSomething(String value)List<Integer> getIDs()。doSomething()方法实际上使用泛型类型作为返回值的类型,但似乎没有引起问题。getIDs()方法返回一个与Task类型无关的List,在使用for..each语句迭代返回值时却导致了问题。出现了以下编译错误。
error: incompatible types
    for (Integer value : task.getIDs()){
required: Integer
found:    Object
似乎接口上的类型擦除导致编译器忘记了第二个方法上声明的类型,这与泛型类型无关。换句话说,为什么接口上的泛型类型会影响编译器如何理解getIDs()方法的返回值,特别是在for..each语句的上下文中?显然,如果我在for..each之外获取列表的引用,则没有问题,但不能直接获取。
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();
}
实现接口并不是展示重点的必要条件,但我不想留下引用Task task = null;,免得回答者告诉我这就是问题所在。
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 );
   }
}

return new int[]{1,2,3,4}或者return Arrays.asList( new Integer(1), new Integer(2), ...);这样的语句会发生什么?(注意,不是假设性的问题。试一下,将结果添加到问题中以显示装箱/拆箱明显做了一些奇怪的事情) - Mike 'Pomax' Kamermans
这是一个非常好的问题;答案可能深藏在JLS的内部。 - arshajii
1
@arshajii:你把U doSomething(String value)改成了U doSomething(U value)吗?我认为发布的代码并没有实际上OP想要做的事情。 - Bhesh Gurung
1
我的猜测是因为您声明了没有泛型的 Task task,它会从类定义中删除所有泛型,包括 getIDs 上的 <Integer>。不确定这是一个错误还是特性。 - Kevin
我已经给所有答案点了赞,因为它们都有助于我理解问题。Lone nebula的答案是一个潜在的解决方法,可以让编译器保持愉快,如果我使用泛型定义我的Task接口。由于问题是为什么会出现这种情况,我接受了这个答案,并引用了规范JLS-4.8-210,但我认为两个引用规范的答案都很有价值。 - Martin Woolstenhulme
感谢@arshajii修复我的代码,我一直在测试接口定义,看看如果泛型类型是方法参数而不是返回类型是否会有差异,但没有效果,我忘记更新最终的示例代码了。 - Martin Woolstenhulme
3个回答

9
发生的情况是当使用一个带有泛型参数<T>的类(或接口),但在没有<T>的情况下引用该实例时(即该raw类型),编译器会从类中擦除所有泛型类型信息。这可能是由于与先前的1.5之前的源代码兼容性有关,在那里您根本无法使用泛型类型信息。

考虑这样一种情况:您正在编写代码并在Java 1.4编译器上进行编译。您想使用一个使用泛型的库。当您将来自具有泛型参数的库的类型作为原始类型引用时,编译器强制使用不带泛型参数的类型。

编辑:

JLS-4.8-210提到了这一点,当它提到(来源:zhong-j-yu):

一个原始类型C的构造函数(§8.8),实例方法(§8.4,§9.4)或非静态字段(§8.3) M,如果不是从其超类或超接口继承而来,则其类型为对应于C的泛型声明中其类型擦除的原始类型。

这仍然感觉像一个陷阱,但很可能有某些原因。


一个原始类型的实例方法的类型是该原始类型,这个原始类型可能不安全。请参考:http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.8-210 - ZhongYu
我相信我已经找到了适当的JLS规范,请看我的回答。 - Kevin

4
错误似乎出现在这里:
Task task = new MyTask();

您忘记在 Task 后面添加泛型。如果您将其更改为以下其中一个,它应该可以工作:
Task<Boolean> task = new MyTask();
Task<?> task = new MyTask();

1
问题是为什么它不能按原样工作。 - arshajii
@arshajii 他们没有足够的资源/时间,所以对原始类型进行了非常粗糙的处理。 - ZhongYu

4
如果我正确理解了Java语言规范(§4.6.类型擦除),那么这是该语言的“坑点”:
引用:

类型擦除还将构造函数或方法的签名(§8.4.2)映射到一个没有参数化类型或类型变量的签名。构造函数或方法签名s的擦除是一个由与s相同名称和在s中给出的所有形式参数类型的擦除组成的签名。

我认为这意味着,如果您声明一个类型(Task),该类型声明了一个泛型参数(Task<U>),而没有该泛型参数,则它所有的函数也会失去其泛型类型,无论它们是否相关。因此,编译器将您的task.getIDs()解释为返回一个普通的List,而不是List<Integer>。当然,迭代器产生的是Objects而不是Integers,导致您看到的编译器错误。
这是因为可能需要与Java 1.5之前生成的代码保持向后兼容性,当时还没有引入泛型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接