Java foreach循环中的ClassCastException

6 java generics foreach iterator classcastexception

在什么情况下可以在下面的代码中发生ClassCastException:

import java.util.Arrays;
import java.util.List;

public class Generics {

    static List getObjects() {
        return Arrays.asList(1, 2, 3);
    }

    public static void main(String[] args) {
        List<String> list = getObjects();
        for (Object o : list) { // ClassCastException?
            System.out.println(o);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们在生产环境中有类似的情况(我知道这是一种不好的做法),客户在注释行提供了带有ClassCastException的日志,但我似乎无法重现它.有什么想法吗?

我知道JVM在使用foreach时会在后台创建一个迭代器,但在某些情况下它是否可以创建一个原始Iterator,而在其他情况下它是否可以创建一个参数化迭代器?

更新:我也看了一下生成的字节码,并在Windows下使用JDK 1.6.0_21-B07无checkcast制成.有趣:)

这是主要方法:

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #34; //Method getObjects:()Ljava/util/List;
   3:   astore_1
   4:   aload_1
   5:   invokeinterface #36,  1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
   10:  astore_3
   11:  goto    28
   14:  aload_3
   15:  invokeinterface #42,  1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
   20:  astore_2
   21:  getstatic   #48; //Field java/lang/System.out:Ljava/io/PrintStream;
   24:  aload_2
   25:  invokevirtual   #54; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   28:  aload_3
   29:  invokeinterface #60,  1; //InterfaceMethod java/util/Iterator.hasNext:()Z
   34:  ifne    14
   37:  return

谢谢大家的答案!

更新2:我误用Eclipse IDE,它使用自己的编译器,所以实际上它上面的字节码是使用Eclipse编译器生成的字节码.看这里如何手动编写代码使用Eclipse.总之,在某些情况下,Eclipse编译器会从Sun编译器生成不同的字节码,无论平台如何,此处描述的情况都是一个.

T.J*_*der 5

那个代码不应该总是抛出一个ClassCastException吗?它对我来说使用 Sun Java 6 编译器和运行时(在 Linux 上)。您将Integers 转换为Strings。创建的迭代器将是 an Iterator<String>,但随后它尝试访问第一个元素,即 an Integer,因此失败。

如果您像这样更改数组,这会变得更清楚:

return Arrays.asList("one", 2, 3);
Run Code Online (Sandbox Code Playgroud)

现在循环实际上适用于第一个元素,因为第一个元素是 aString并且我们看到了输出;然后Iterator<String>第二个失败,因为它不是一个字符串。

如果您只使用泛型List而不是特定的,您的代码就可以工作:

List list = getObjects();
for (Object o : list) {
    System.out.println(o);
}
Run Code Online (Sandbox Code Playgroud)

...或者,当然,如果您使用List<Integer>,因为内容是Integers。你现在正在做的事情会触发编译器警告——这是有Note: Generics.java uses unchecked or unsafe operations. 充分理由的。

此修改也有效:

for (Object o : (List)list)
Run Code Online (Sandbox Code Playgroud)

...大概是因为那时你正在处理一个Iterator,而不是一个Iterator<String>

bozho 说他在 Windows XP 上没有看到这个错误(没有提到哪个编译器和运行时,但我猜是 Sun 的),你说你没有看到它(或不可靠),所以很明显有一些实现灵敏度在这里,但底线是:不使用List<String>同一个互动ListInteger秒。:-)

这是我正在编译的文件:

import java.util.Arrays;
import java.util.List;

public class Generics {

    static List getObjects() {
        return Arrays.asList("one", 2, 3);
    }

    public static void main(String[] args) {
        List<String> list = getObjects();
        for (Object o : list) { // ClassCastException?
            System.out.println(o);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是汇编:

tjc@forge:~/temp$ javac Generics.java 
注意:Generics.java 使用未经检查或不安全的操作。
注意:使用 -Xlint:unchecked 重新编译以获取详细信息。

这是运行:

tjc@forge:~/temp$ java 泛型 
一
线程“main”中的异常 java.lang.ClassCastException:java.lang.Integer 不能转换为 java.lang.String
    在 Generics.main(Generics.java:12)

第 12 行是for语句。请注意,它确实输出了第一个元素,因为我将其更改为String. 它没有输出其他人。(在我进行更改之前,它立即失败了。)

这是我正在使用的编译器:

tjc@forge:~/temp$ which javac
/usr/bin/javac
tjc@forge:~/temp$ ll /usr/bin/javac
lrwxrwxrwx 1 root root 23 2010-09-30 16:37 /usr/bin/javac -> /etc/alternatives/javac*
tjc@forge:~/temp$ ll /etc/alternatives/javac
lrwxrwxrwx 1 root root 33 2010-09-30 16:37 /etc/alternatives/javac -> /usr/lib/jvm/java-6-sun/bin/javac*

这是反汇编,它显示了checkcast

tjc@forge:~/temp$ javap -c 泛型
从“Generics.java”编译
公共类泛型扩展 java.lang.Object{
公共泛型();
  代码:
   0:aload_0
   1:调用特殊#1;//方法java/lang/Object."":()V
   4:返回

静态 java.util.List getObjects();
  代码:
   0:iconst_3
   1:新数组#2;//类 java/io/Serializable
   4:重复
   5:iconst_0
   6:ldc #3;//字符串一
   8:aastore
   9:重复
   10:iconst_1
   11:iconst_2
   12:调用静态#4;//方法java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   15:aastore
   16:重复
   17:iconst_2
   18:iconst_3
   19:调用静态#4;//方法java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   22:aastore
   23:调用静态#5;//方法java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   26:回来

public static void main(java.lang.String[]);
  代码:
   0:调用静态#6;//方法getObjects:()Ljava/util/List;
   3:astore_1
   4:aload_1
   5: 调用接口 #7, 1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
   10:astore_2
   11:aload_2
   12: 调用接口 #8, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
   17: ifeq 40
   20:aload_2
   21: 调用接口 #9, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
   26:checkcast #10;//类java/lang/String
   29:astore_3
   30:获取静态 #11;//字段 java/lang/System.out:Ljava/io/PrintStream;
   33:aload_3
   34:调用虚拟#12;//方法java/io/PrintStream.println:(Ljava/lang/Object;)V
   37:转到 11
   40:返回

}

但同样,底线必须是:不要使用 aList<String>List包含不是Strings 的东西的a进行交互。:-)

  • 这是一个相当有趣的讨论,但我认为我们都同意代码有什么问题以及必须修复的内容:) (3认同)
  • 我运行了它,它没有抛出异常:)、1.6.0_20、Windows XP (2认同)