8

语言:Java
编译器版本:1.6

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

  1. 创建一个List<String>
  2. 添加一个String
  3. 分配List<String>给原始List
  4. 创建一个List<Integer>
  5. 将原始分配ListList<Integer>
  6. 添加一个Integer
  7. get()使用@indexes 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)

这是我的代码

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

2 回答 2

5

我会认为这是一个编译器错误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 语言规范 (14.14.2)表明这种转换应该是 to 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检查的强制转换应该是 to Object, not Integer

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

跟进

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

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

以下是Java 6 及更早版本的增强 for 语句应该翻译的内容:

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

如您所见,这里没有指定检查演员表。所以错误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。

于 2012-09-09T15:38:07.743 回答
1

代码

    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 添加到其中的原因。

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

于 2012-09-09T15:17:12.253 回答