【问题标题】:Iterate parameterized List (after raw List type assignment)迭代参数化列表(在原始列表类型分配之后)
【发布时间】:2012-09-02 15:21:05
【问题描述】:

语言:Java
编译器版本:1.6

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

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

所有语句都在编译(带有警告)并且运行良好。

但如果我尝试使用for 循环遍历List<Integer>,我会得到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)

这是我的代码

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

【问题讨论】:

标签: java generics classcastexception foreach


【解决方案1】:

我认为这是javac 中的编译器错误。正在插入已检查的演员表。我们可以用javap -c CheckRawTypeAdd反汇编类看到这个(cast是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

但是,Java Language Spec (14.14.2) 表示此演员表应该是Object,而不是Integer。它首先通过语法定义术语:

EnhancedForStatement:
    for ( FormalParameter : Expression ) Statement

FormalParameter:
    VariableModifiersopt Type VariableDeclaratorId

VariableDeclaratorId:
    Identifier
    VariableDeclaratorId []

所以在我们的例子中,TypeObject。然后它继续说这被翻译成什么:

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

所以这里相关的是TargetType 的分辨率。这也在 JLS 中定义:

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

因为Object 肯定是一个引用类型,那么TargetType 就是Object,所以检查的强制转换应该是Object,而不是Integer

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

跟进

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

另外,我应该注意两点。 首先,我在上面给出的 JLS 参考是在最新的 JLS 中,并且该部分实际上已针对 Java 7 进行了更改(针对此错误!)

这是一个增强的 for 语句应该翻译成for Java 6 and earlier

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

如您所见,此处指定了 no 已检查演员表。所以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);
}

正确强制转换为CharSequence,而不是String。我最初使用 JDK 6 进行编译,而不是 JDK 7。

【讨论】:

  • +1 好答案。我错过了If Type (in the FormalParameter production) is a reference type, then TargetType is Type的部分
【解决方案2】:

代码

    for(Object o : list_int)
    {
      System.out.println("o value is"+o);
    }

相当于这样的:

for (Iterator<Integer> it = list.iterator(); it.hasNext();) {
    Integer o = it.next();
    System.out.println("o value is"+o);
}

如您所见,Iterator 是通用的,因此将值转换为其参数类型(Integer 我们的例子)。

所以,幕后的Integer o = it.next(); 行执行以下操作:

Integer o = (Integer)it.next();

我认为现在很明显ClassCastException 是如何产生的。确实,您设法将字符串值插入到列表中,因此当it.next() 返回您的字符串时,转换失败。

所以,“你是如何设法将字符串插入 int 列表”的问题仍然存在。 答案是泛型是编译器的魔法。他们的另一个名字是擦除。 Java 字节码不包含有关列表类型的信息。它只包含在需要时转换为具体类型的内容。这就是您设法将参数化列表分配给原始列表然后将 sting 添加到其中的原因。

正如您正确提到的,您看到了警告。结论是“不要忽略编译警告。”

【讨论】:

  • 这是一种轶事,但为什么是Integer o = it.next() 而不是Object o = it.next()?同样与您所说的相反,Iterator 什么都不做,因为泛型是仅编译时的构造。
  • 运行无异常。我认为 OP 在那个 for 循环中用 Object 替换了 Integer
  • @Bhesh,不,我遇到了运行时故障。您使用的是 Eclipse 编译器吗?我正在使用 javac。
  • @MarkPeters:是的,我正在使用 Eclipse 编译器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-05
  • 1970-01-01
  • 2010-12-06
  • 1970-01-01
  • 2021-11-29
  • 1970-01-01
相关资源
最近更新 更多