省略<?>会违反此规范

Mic*_*ski 11 java generics foreach

我创建了一个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();
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器错误是:

Error:(9, 51) java: incompatible types: java.lang.Object cannot be converted to java.lang.String
Run Code Online (Sandbox Code Playgroud)

更改违规行中的定义MyEntity myEntityMyEntity<?> myEntity解决问题.我想知道为什么这个for-each的返回类型被视为an Object而不是a String,除非我将通配符添加到父类?请注意,getLabels()它本身不包含泛型.

根据第14.14.2章.在Java语言规范中,使用迭代器将for-each编译为循环.有趣的是,扩大的for-each于这样的迭代器手动工作:

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

有谁能解释为什么?

And*_*niy 9

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

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

为什么会这样?因为当你声明变量myEntity(在for循环中或在我的例子中MyEntity myEntity,无关紧要)时,你将它声明为原始类型,这也从方法的返回类型中消除泛型类型getLabels:所以它变得像List getLabels();,显然Object类型是for循环结构.

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


JLS 4.8"Raw types"中给出了非常类似的例子,它解释了为什么会发生这种情况:

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

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

class Outer<T>{
     class Inner<S> {
         S s;
     }
}
Run Code Online (Sandbox Code Playgroud)

无法将Inner作为部分原始类型("稀有"类型)访问:

Outer.Inner<Double> x = null; // illegal
Run Code Online (Sandbox Code Playgroud)

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
    }
}
Run Code Online (Sandbox Code Playgroud)

更仔细地解释为什么Iterator<String> iterator = myEntity.getLabels().iterator();JLS的工作基于此规则:

也就是说,Java编程语言的子类型规则(第4.10.2节)使得原始类型的变量可以被赋予任何类型的参数化实例的值.

同样,您可以始终将编译良好的代码编写为:

    List<String> labels = myEntity.getLabels();
    for (String label : labels) { // <-- OK, no error here
            /* ... */
    }
Run Code Online (Sandbox Code Playgroud)