我创建了一个MWE,通过添加<?>
来更改单行解决了编译错误。
以下代码无法编译:
import java.util.List;
public class MainClass {
public void traverse() {
List<MyEntity> list = null /* ... */;
for (MyEntity myEntity : list) {
for (String label : myEntity.getLabels()) { // <-- Offending Line
/* ... */
}
}
}
interface MyEntity<T> {
T get();
List<String> getLabels();
}
}
编译错误是:
Error:(9, 51) java: incompatible types: java.lang.Object cannot be converted to java.lang.String
将违规行中的定义从MyEntity myEntity
更改为MyEntity<?> myEntity
可解决此问题。我想知道为什么这个for-each的返回类型被视为Object
而不是String
,除非我将通配符添加到父类?请注意,getLabels()
本身不包含泛型。
根据Chapter 14.14.2. of the Java Language Specification,使用迭代器将for-each编译为循环。有趣的是,手动工作:
将for-each扩展为这样的迭代器Iterator<String> iterator = myEntity.getLabels().iterator();
while (iterator.hasNext()) {
String label = iterator.next();
/* ... */
}
任何人都可以解释原因吗?
答案 0 :(得分:9)
首先,代码示例的方法体可以简化为:
public void traverse() {
MyEntity myEntity = null;
for (String label : myEntity.getLabels()) { // <-- Offending Line
/* ... */
}
}
为什么会这样?因为当您声明变量myEntity
(在for循环中或在我的示例中与MyEntity myEntity
无关时),您将其声明为 raw 类型,它从方法getLabels
的返回类型中删除了泛型类型:因此它变得像List getLabels();
,其中for循环构造显然需要Object
类型。
同时Iterator<String> iterator = myEntity.getLabels().iterator();
工作正常,因为您明确指定了此类型:Iterator<String>
。
在JLS 4.8 "Raw types"中给出了非常相似的例子,解释了为什么会这样:
...依赖于类型变量的继承类型成员将是 由于规则的原因而继承为原始类型 原始类型的超类型被删除......
上述规则的另一个含义是通用的内部类 原始类型本身只能用作原始类型:
class Outer<T>{
class Inner<S> {
S s;
}
}
无法将Inner作为部分原始类型访问(“罕见” 型):
Outer.Inner<Double> x = null; // illegal
UPD-2 :当我收到有关Iterator<String> iterator = myEntity.getLabels().iterator();
的问题时,为什么可以这样做,而第一个例子不起作用?
我个人同意这看起来令人困惑。但这是规则。本案例也包含在同一JLS段落中:
class Cell<E> {
E value;
Cell(E v) { value = v; }
E get() { return value; }
void set(E v) { value = v; }
public static void main(String[] args) {
Cell x = new Cell<String>("abc");
System.out.println(x.value); // OK, has type Object
System.out.println(x.get()); // OK, has type Object
x.set("def"); // unchecked warning
}
}
更详细地解释为什么Iterator<String> iterator = myEntity.getLabels().iterator();
来自JLS的工作基于以下规则:
即Java编程的子类型规则(§4.10.2) 语言使得可以分配原始类型的变量 任何类型的参数化实例的值
同样,您总是可以编写编译良好的代码:
List<String> labels = myEntity.getLabels();
for (String label : labels) { // <-- OK, no error here
/* ... */
}