Java foreach循环中的ClasscastException
-
28-09-2019 - |
题
在什么情况下,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);
}
}
}
我们在生产环境中也有类似的案例(我知道不良练习),并且客户在评论中提供了一条classcastException的日志,但我似乎无法再现它。有什么想法吗?
我知道JVM在使用Foreach时会在后台创建迭代器,但是在某些情况下可以创建原始迭代器,在其他情况下可以创建一个参数化的迭代器?
更新:我还使用JDK 1.6.0_21-B07否查看了生成的字节码和窗口上的字节码 检查广播 被制作了。有趣的 :)
这是主要方法:
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编译器都会产生不同的字节代码,此处描述的是一种。
解决方案
不应该那个代码 总是 扔 ClassCastException
?它对我使用Sun Java 6编译器和运行时(在Linux上)确实如此。你正在铸造 Integer
s as String
s。创建的迭代器将是 Iterator<String>
, ,但随后它试图访问第一个元素,这是 Integer
, ,因此失败了。
如果您这样更改数组,这将变得更加清晰:
return Arrays.asList("one", 2, 3);
现在,循环实际上适用于第一个元素,因为第一个元素是 String
我们看到了输出;然后 Iterator<String>
第二个失败,因为它不是字符串。
如果您只使用通用,您的代码有效 List
而不是特定的:
List list = getObjects();
for (Object o : list) {
System.out.println(o);
}
...或者,如果您使用的话 List<Integer>
, ,因为内容是 Integer
s。您现在正在做的事情触发了编译器警告 - Note: Generics.java uses unchecked or unsafe operations.
- 有充分的理由。
此修改也有效:
for (Object o : (List)list)
...大概是因为那时您正在处理 Iterator
, ,不是 Iterator<String>
.
Bozho说过他在Windows XP上没有看到此错误(没有提及哪个编译器和运行时,但我猜Sun),您说您没有看到它(或没有可靠),所以显然有一些在这里实现敏感性,但最重要的是:不要使用 List<String>
与一个 List
的 Integer
s。 :-)
这是我要编译的文件:
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);
}
}
}
这是汇编:
tjc@forge:~/temp$ javac Generics.java Note: Generics.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.
这是运行:
tjc@forge:~/temp$ java Generics one Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at 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 Compiled from "Generics.java" public class Generics extends java.lang.Object{ public Generics(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return static java.util.List getObjects(); Code: 0: iconst_3 1: anewarray #2; //class java/io/Serializable 4: dup 5: iconst_0 6: ldc #3; //String one 8: aastore 9: dup 10: iconst_1 11: iconst_2 12: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 15: aastore 16: dup 17: iconst_2 18: iconst_3 19: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 22: aastore 23: invokestatic #5; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List; 26: areturn public static void main(java.lang.String[]); Code: 0: invokestatic #6; //Method getObjects:()Ljava/util/List; 3: astore_1 4: aload_1 5: invokeinterface #7, 1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 10: astore_2 11: aload_2 12: invokeinterface #8, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 17: ifeq 40 20: aload_2 21: invokeinterface #9, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 26: checkcast #10; //class java/lang/String 29: astore_3 30: getstatic #11; //Field java/lang/System.out:Ljava/io/PrintStream; 33: aload_3 34: invokevirtual #12; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 37: goto 11 40: return }
再说一次,最重要的是:不要使用 List<String>
与一个 List
其中包含的东西不 String
s。 :-)
其他提示
我也无法复制它,但是我发现了必须纠正的以下错误:
- 制作
getObjects()
返回List<Integer>
, ,而不是原始类型 - 不要期望
List<String>
, ,但是List<Integer>
反而 - 迭代时,循环
for (Integer o : list)
问题是方法 static List getObjects() {
返回通用(未参数化) List
. 。然后您将其分配给 List<String>
. 。这条线应发出编译器警告,这应该标志着问题。
这部分 :
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
将简化为
List<String> list = getObjects();
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
Object o = iterator.next();
System.out.println(o);
}
但是迭代器的实现将在调用 next()
方法内容发送给 Iterator
在一个 String
.
这就是为什么您有CCE。
解决方案:
要么在任何地方使用仿制药,要么不使用它们,但是保持一致确实很重要。如果您还退还了 List<Integer>
或a List<? super Integer>
您会在编译时看到这个问题。