问题 省略会直接破坏此代码


我创建了一个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() 本身不包含泛型。

根据 第14.14.2节。 Java语言规范,使用迭代器将for-each编译为循环。有趣的是,手动将for-each扩展为这样的迭代器 作品

Iterator<String> iterator = myEntity.getLabels().iterator();
while (iterator.hasNext()) {
    String label = iterator.next();
    /* ... */
}

有人可以解释为什么吗


4045
2018-02-15 10:02


起源

好的,以前从未见过 - Andremoniy


答案:


首先,代码示例的方法体可以简化为:

public void traverse() {
    MyEntity myEntity = null;
    for (String label : myEntity.getLabels()) { // <--  Offending Line
            /* ... */
    }
}

为什么会这样?因为当你声明变量时 myEntity (无论在哪里 - 在for循环中或在我的例子中)都是如此 MyEntity myEntity,你声明它 生的 type,从方法的返回类型中删除泛型类型 getLabels 同样:它变得像 List getLabels();,显然 Object 类型是for循环结构。

在同一时间 Iterator<String> iterator = myEntity.getLabels().iterator(); 工作正常,因为您明确指定类型: Iterator<String>


非常类似的例子给出了 JLS 4.8“原始类型” 这解释了为什么会发生:

...依赖于类型变量的继承类型成员   由于规则的原因而继承为原始类型   原始类型的超类型被删除...

上述规则的另一个含义是通用的内部类   原始类型本身只能用作原始类型:

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
            /* ... */
    }

9
2018-02-15 10:13



谢谢!我需要进一步澄清:假设,如你所说,宣布 myEntity 因为raw也会使所有成员都是原始的,我怎样才能存储结果 myEntity.getLabels() 在(非原始!) Iterator<String>?这不也不违法吗? - Michael Borkowski
这有点不同的情况。 Iterator<E> iterator() 包含类型变量,而不是泛型类型。当您指定此类型时 Iterator<String> iterator 你实际上是“恢复”这种类型,这就是为什么它的工作原理 list 它本身就是原始类型。 但,因为它不是指定类型的直接方式,它将为您提供编译时 未经检查的警告。 - Andremoniy
令人困惑的是 List<String> labels = myEntity.getLabels() 然后是编译器的A-OK。是因为在任务中允许某种类型的强制? - Fabien Benoit-Koch
@ FabienBenoit-Koch请看看我的更新,可能它可以给你一些线索 - Andremoniy
@Andremoniy你的评论中的“恢复”部分为我澄清了这一点。所以,多亏了你的回答,我了解到声明一个原始类型 所有 其成员原始的,甚至是与类本身的类型参数无关的成员。谢谢!! - Michael Borkowski