6

下面代码中什么情况下会出现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 时会在后台创建一个迭代器,但它可以在某些情况下创建一个原始迭代器,在其他情况下创建一个参数化迭代器吗?

更新我还查看了生成的字节码,在 Windows 上,使用 JDK 1.6.0_21-b07 没有进行检查。有趣的 :)

下面是主要方法:

公共静态无效主(java.lang.String[]);
  代码:
   0:调用静态#34;//方法getObjects:()Ljava/util/List;
   3:astore_1
   4:aload_1
   5: 调用接口#36, 1; //接口方法 java/util/List.iterator:()Ljava/util/Iterator;
   10:astore_3
   11:转到 28
   14:加载_3
   15: 调用接口#42, 1; //接口方法 java/util/Iterator.next:()Ljava/lang/Object;
   20:astore_2
   21:获取静态#48;//字段 java/lang/System.out:Ljava/io/PrintStream;
   24:加载_2
   25:调用虚拟#54;//方法java/io/PrintStream.println:(Ljava/lang/Object;)V
   28:加载_3
   29: 调用接口#60, 1; //接口方法 java/util/Iterator.hasNext:()Z
   34:如果 14
   37:返回

谢谢大家的回答!

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

4

4 回答 4

5

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

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

return Arrays.asList("one", 2, 3);

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

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

List list = getObjects();
for (Object o : list) {
    System.out.println(o);
}

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

此修改也有效:

for (Object o : (List)list)

...大概是因为那时您正在处理的是Iterator,而不是Iterator<String>.

bozho 说他在 Windows XP 上没有看到这个错误(没有提到哪个编译器和运行时,但我猜是 Sun 的),你说你没有看到它(或不可靠),所以很明显有一些这里的实现敏感性,但底线是:不要用于List<String>与 a Listof Integers 交互。:-)

这是我正在编译的文件:

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
注意:Generics.java 使用未经检查或不安全的操作。
注意:使用 -Xlint:unchecked 重新编译以获取详细信息。

这是运行:

tjc@forge:~/temp$ java 泛型
一
线程“主”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:最不发达国家#3;//字符串一
   8:商店
   9:重复
   10:iconst_1
   11:iconst_2
   12:调用静态#4;//方法 java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   15:商店
   16:重复
   17:图标st_2
   18:图标st_3
   19:调用静态#4;//方法 java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   22:商店
   23:调用静态#5;//方法 java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   26:返回

公共静态无效主(java.lang.String[]);
  代码:
   0:调用静态#6;//方法getObjects:()Ljava/util/List;
   3:astore_1
   4:aload_1
   5:调用接口#7、1;//接口方法 java/util/List.iterator:()Ljava/util/Iterator;
   10:astore_2
   11:加载_2
   12:调用接口#8、1;//接口方法 java/util/Iterator.hasNext:()Z
   17:如果当量 40
   20:aload_2
   21:调用接口#9、1;//接口方法 java/util/Iterator.next:()Ljava/lang/Object;
   26:检查广播#10;//类java/lang/String
   29:astore_3
   30:获取静态#11;//字段 java/lang/System.out:Ljava/io/PrintStream;
   33:加载_3
   34:调用虚拟#12;//方法java/io/PrintStream.println:(Ljava/lang/Object;)V
   37:转到 11
   40:返回

}

不过,底线必须是:不要使用 aList<String>List包含非Strings 内容的 a 交互。:-)

于 2010-10-07T11:12:59.903 回答
1

我也无法重现它,但我发现了以下必须纠正的错误:

  • make getObjects()return List<Integer>,而不是原始类型
  • 不要指望,List<String>而是一个List<Integer>
  • 迭代时,循环for (Integer o : list)
于 2010-10-07T11:14:14.580 回答
0

问题是该方法static List getObjects() {返回一个泛型(非参数化)List。你把它分配给List<String>. 这一行应该给出编译器警告,这应该表明存在问题。

于 2010-10-07T11:15:46.083 回答
0

这部分 :

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

但是 Iterator 的实现会在调用a中next()的内容发送的方法时尝试强制转换。IteratorString

这就是您拥有 CCE 的原因。

解决方案:

要么在任何地方使用泛型,要么不使用它们,但保持一致非常重要。如果你返回了 aList<Integer>或 aList<? super Integer>你会在编译时看到这个问题。

于 2010-10-07T11:20:34.767 回答