迭代参数化List(在原始List类型赋值之后)

pul*_*thi 8 java generics foreach classcastexception

语言:Java
编译器版本:1.6

在下面的代码中,我尝试执行以下操作:

  1. 创建一个 List<String>
  2. 添加一个 String
  3. 分配List<String>到原始List
  4. 创建一个 List<Integer>
  5. 将原始分配ListList<Integer>
  6. 添加一个 Integer
  7. 使用get()@index 1和2 检索值并打印它们.

所有语句都在编译(带警告)并运行正常.

但是,如果我尝试循环List<Integer>使用for循环,我会得到一个ClassCastException.我只是想知道为什么它允许我使用list.get()方法但不允许我迭代它?

输出:( 如果我使用未注释的for循环运行)abcd 200

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot  be cast to java.lang.Integer
        at genericsamples.CheckRawTypeAdd.main(CheckRawTypeAdd.java:26)
Run Code Online (Sandbox Code Playgroud)

这是我的代码

import java.util.*;
import java.io.*;
class CheckRawTypeAdd
{
    public static void main(String[] sr)
    {   
        List<String> list_str = new ArrayList<String>();
        list_str.add("abcd");
        List<Integer> list_int = new ArrayList<Integer>();  
        List list_raw; 
        list_raw=list_str;
        list_int=list_raw;
        list_int.add(200);
        Object o1 = list_int.get(0);
        Object o2 = list_int.get(1);        
        System.out.println(o1);
        System.out.println(o2);
        //for(Object o : list_int)
        //{
        //  System.out.println("o value is"+o);
        //}
    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 5

我认为这是一个编译器错误javac.插入已检查的演员表.我们可以看到这javap -c CheckRawTypeAdd用于反汇编类(强制转换为101;请注意我在编译之前取出了一些不需要的代码行,因此代码点会有所不同):

  77: invokeinterface #10,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
  82: astore        6
  84: aload         6
  86: invokeinterface #11,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
  91: ifeq          109
  94: aload         6
  96: invokeinterface #12,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
 101: checkcast     #13                 // class java/lang/Integer
Run Code Online (Sandbox Code Playgroud)

但是,Java语言规范(14.14.2)表明此强制转换应该是Object,而不是Integer.它首先通过语法定义术语:

EnhancedForStatement:
    for ( FormalParameter : Expression ) Statement

FormalParameter:
    VariableModifiersopt Type VariableDeclaratorId

VariableDeclaratorId:
    Identifier
    VariableDeclaratorId []
Run Code Online (Sandbox Code Playgroud)

所以在我们的例子中,TypeObject.然后它继续说明这被转化为:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    VariableModifiersopt TargetType Identifier =
        (TargetType) #i.next();
    Statement
}
Run Code Online (Sandbox Code Playgroud)

所以这里有关的是解决方案TargetType.这也在JLS中定义:

如果Type(在FormalParameter生产中)是引用类型,则TargetType是Type

由于Object肯定是引用类型,则TargetTypeObject等的检查转换应该是Object,不Integer.

在这个线程中的其他人进一步证明了这是一个错误,并指出如果使用ecj(Eclipse的编译器)则不会发生这个问题.但是,我知道这对Oracle编译器团队来说是一个低优先级的错误,因为你必须滥用泛型来运用它.人们几乎会说这是一个功能,而不是一个bug.

跟进

为了最终确认这是一个错误,这是针对此确切问题的现有错误报告:

另外,我应该注意两件事. 首先,我在上面给出的JLS引用是在最新的JLS中,并且该部分实际上已经针对Java 7进行了更改(以回应此错误!)

以下是针对Java 6及更早版本应该转换为增强的for语句的内容:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
        VariableModifiersopt Type Identifier = #i.next();
   Statement
}
Run Code Online (Sandbox Code Playgroud)

如您所见,此处指定任何选中的强制转换.因此,错误javac并不在于它正在进行错误的演员,而是它正在进行任何演员表演.

其次,在Java 7中,javac根据JLS SE 7规范正确编译代码(这是我上面引用的).因此,以下代码有效:

List<String> list_str = new ArrayList<String>();
((List) list_str).add(new StringBuilder(" stringbuilder"));
for (CharSequence o : list_str) {
   System.out.println("o value is" + o);
}
Run Code Online (Sandbox Code Playgroud)

正确的演员CharSequence,不是String.我最初使用JDK 6进行编译,而不是JDK 7.