省略<! - ? - >无意义地破坏了这段代码

时间:2017-02-15 10:02:25

标签: 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();
    }
}

编译错误是:

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

任何人都可以解释原因吗?

1 个答案:

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