【问题标题】:Why does the Java Compiler complain on using foreach with a raw type? [duplicate]为什么 Java 编译器会抱怨将 foreach 与原始类型一起使用? [复制]
【发布时间】:2011-07-23 03:22:33
【问题描述】:

在 Java 的 for-each 循环中使用泛型时,我遇到了一个奇怪的编译器错误。这是 Java 编译器的错误,还是我真的在这里遗漏了什么?

这是我的全班:

public class Generics<T extends Object> {
  public Generics(T myObject){
    // I didn't really need myObject
  }

  public List<String> getList(){
    List<String> list = new ArrayList<String>();
    list.add("w00t StackOverflow");
    return list;
  }

  public static void main(String...a){
    Generics generics = new Generics(new Object());
    for(String s : generics.getList()){
      System.out.println(s);
    }
  }
}

编译器抱怨 for-each 行:“类型不匹配无法从元素类型 Object 转换为 String。”
如果我做出这个细微的改变,它会编译:

public static void main(String...a){
  Generics<?> generics = new Generics(new Object());
  for(String s : generics.getList()){
    System.out.println(s);
  }
}

我知道getList() 确实使用了泛型,但它以我认为完全不相关的方式使用它们。如果我试图迭代 T 类型的东西并且getList() 返回List&lt;T&gt; 或其他东西,我可以理解这一点,但这里不是这种情况。 getList() 的返回类型应该与 T 完全无关,并且不应该关心我是否将原始类型用于我的 Generics 对象......对吗?这些不应该是完全不相关的,还是我真的在这里遗漏了什么?

请注意,如果我这样做,代码也会编译,我认为这也应该等同于第一个:

public static void main(String...a){
  Generics generics = new Generics(new Object());
  List<String> list = generics.getList();
  for(String s : list){
    System.out.println(s);
  }
}

【问题讨论】:

  • &lt;T extends Object&gt;&lt;T&gt; 没有什么不同。您不是在制作类的通用版本,而是在制作原始类型。这给我们带来了一个问题,为什么你的类首先是通用的?您使用 T 的唯一地方是在构造函数中,并且您不使用该引用。
  • 我使用了&lt;T extends Object&gt;,因为我只是需要一些例子。真正的代码显然是别的东西,它确实使用了 T...它只是以与 getList() 完全无关的方式使用 T。
  • 与您的问题无关,但我会将构造函数设为 Generics cls),这样您就不必实例化 T 类型的对象来构造这个 Generics 类。
  • 我真的只是想展示&lt;T extends *something*&gt;,而不是把重点放在那是什么,所以我选择Object作为我的例子。

标签: java generics foreach raw-types


【解决方案1】:

不同之处在于,当您使用原始类型时,所有成员签名中的通用引用也会转换为原始形式。如此有效地调用了一个方法,该方法现在具有这样的签名:

List getList()

现在至于为什么你的最终版本编译 - 虽然它编译,如果你使用 -Xlint 会有一个警告:

Generics.java:16: warning: [unchecked] unchecked conversion
    List<String> list = generics.getList();
                                        ^

这类似于:

 List list = new ArrayList();
 List<String> strings = list;

... 也可以编译,但在 -Xlint 下会出现警告。

故事的寓意:不要使用原始类型!

【讨论】:

  • 我很惊讶所有成员签名中的通用引用都被转换为它们的原始形式。这样做的原因是什么(除了 Sun 只是喜欢它)?
  • @Michael:JLS 在第 4.8 节(原始类型)中包含了这个讨论:“原始类型与通配符密切相关。两者都基于存在类型。原始类型可以被认为是通配符,其类型规则故意不健全,以适应与遗留代码的交互。”换句话说,原始类型通常不应该出现在新代码中,但它们尽量避免使旧代码无法编译,即使它至少是可疑的。
  • 非常有趣。我已经知道要避免使用原始类型(一位同事编写了代码来声明变量),但这凸显了它确实很重要。
  • @michael-mcgowan 我猜他们曾经对泛型的大量细节感到厌倦,而这正是他们认为可以偷工减料并节省一些时间的地方。如果他们有更多的时间/精力,他们就不会让它处于如此草率的状态。
【解决方案2】:

换行

Generics generics = new Generics(new Object());

Generics<?> generics = new Generics<Object>(new Object());

问题的根源在于您使用的是raw type,因此getList 方法的类型是List,而不是List&lt;String&gt;

【讨论】:

  • 泛型不会是字符串类型的泛型......这就是重点。字符串与泛型的类型无关。无论 T 是什么,getList() 都应该返回一个 List&lt;String&gt;
  • @Michael McGowan,说得好。但是在声明时必须有某种类型与类型参数相关联。 Generics&lt;?&gt; generics = new Generics(...); 可以,以不安全的转换警告为模。
  • @Michael McGowan,请注意,如果您所做的只是从类声明中删除类型参数 &lt;T extends Object&gt;,那么它会起作用。
【解决方案3】:

我对您的代码做了一些调整。 您在评论中看到您的构造函数中不需要 Object,因此让我们删除它以避免任何混淆。其次,如果Generics要泛型,请正确初始化它

这是新的主要外观

public static void main(String...a){
    Generics<String> generics = new Generics<String>();
    for(String s : generics.getList()){
      System.out.println(s);
    }
  }

【讨论】:

  • 泛型不会是字符串类型的泛型......这就是重点。字符串与泛型的类型无关。
  • 我想你误解了我的意思。如果您查看代码,您会发现 getList() 方法返回一个 List。如果我们想让代码真正干净,我们可以从代码中剥离泛型部分,而不是在 getList() 方法中。你是在寻求帮助来编译它,而不是他的方法是对还是错。
  • 如果您将通用减速留在类级别,则打开 getList() 方法以删除现在存在的硬编码。
猜你喜欢
  • 1970-01-01
  • 2016-04-22
  • 2012-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-16
  • 1970-01-01
相关资源
最近更新 更多