泛型与遗留代码的兼容性 - 为什么 foreach 在运行时失败而迭代器工作正常

Compatibility of Generics with legacy code - why does the foreach blow at runtime while iterator works fine

我有以下测试代码。我试图了解泛型和遗留之间的互操作性。

List myList = new ArrayList();
myList.add("abc");
myList.add(1);
myList.add(new Object());

System.out.println("Printing the unchecked list");
Iterator iterator = myList.iterator();
while (iterator.hasNext()) {
  System.out.println(iterator.next());
}

List<String> strings = myList;

System.out.println("Printing the unchecked list assigned to List<String> using iterator");
Iterator stringIterator = strings.iterator();
while (stringIterator.hasNext()) {
  System.out.println(stringIterator.next());  // this works fine! why? shouldn't it fail converting the 1 (int/Integer) to String?
}

System.out.println("Printing the unchecked list assigned to List<String> using for");
for (int i = 0; i != strings.size(); i++) {
  System.out.println(strings.get(i));  // blows up as expected in the second element, why?
}

System.out.println("Printing the unchecked list assigned to List<String> using foreach");
for (String s : strings) {
  System.out.println(s);  // blows up as expected in the second element, why?
}

为什么 iterator.next 在我尝试打印时工作正常,而 System.out.println 在我使用 for 循环迭代时按预期爆炸?

关于泛型要记住的关键是它只是一种从源代码中省略强制转换的方法。强制转换由编译器插入。因此,代码失败(或没有失败)是因为存在(或不存在)强制转换。

  1. this works fine! why?

因为 stringIterator 是原始的,所以 stringIterator.next() 没有被转换为任何东西:它只是被读作 Object,这是被擦除的 return 类型。

  1. blows up as expected in the second element, why?

strings是一个List<String>,所以strings.get(i)的结果假设为String,选择调用println(String),而不是比 println(Object)。因此,插入了演员表。 strings.get(1) 不是 String,因此失败并显示 ClassCastException

有趣的是,如果您使用 List<Integer> 尝试此操作,则不会失败,因为会调用 println(Object),然后就不需要强制转换了。

  1. blows up as expected in the second element, why?

因为插入了对 String 的转换,所以要将元素分配给 String s

增强for循环的脱糖形式参考JLS 14.14.2

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    {VariableModifier} TargetType Identifier =
        (TargetType) #i.next();
    Statement
}

因此,您的代码等同于:

for (Iterator<String> it = strings.iterator(); it.hasNext(); ) {
  String s = (String) it.next();  // Actually, your code fails on this line.
  System.out.println(s);
}