【问题标题】:ClassCastException in Java foreach loopJava foreach 循环中的 ClassCastException
【发布时间】:2011-04-22 07:31:33
【问题描述】:

下面代码中什么情况下会出现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 no checkcast 制作完成。有趣:)

这里是主要方法:

公共静态无效主(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:我被使用 own compiler 的 Eclipse IDE 误导了,所以实际上它上面的字节码是使用 Eclipse 编译器生成的。看here如何用Eclipse手动编译代码。 总之,Eclipse 编译器在某些情况下会生成与 Sun 编译器不同的字节码,与平台无关,这里描述的情况是一种。

【问题讨论】:

  • Java 泛型正在咬你! ;-)
  • 提示:(1) 使用您可以使用的所有信息 - 异常应该告诉您涉及哪些类,例如java.lang.ClassCastException: XXXX cannot be cast to YYYY。 (2) 行号有时会偏离几行,因此请查看报告行上方和下方的几行,以了解可能的异常来源。
  • 确实,给出确切的信息。
  • 上面的代码是生产代码的模仿,但“翻译”错误是 ClassCastException Integer cannot be cast to String
  • 感谢您发布反汇编。 checkcast 出现在某些编译版本中(我的,Tim 使用 1.6.0_21 时的版本)而不是其他版本(bozho 的,你的),这很有趣……

标签: java generics iterator foreach classcastexception


【解决方案1】:

该代码不应该总是抛出ClassCastException吗?它适用于我使用 Sun Java 6 编译器和运行时(在 Linux 上)。您将Integers 投射为Strings。创建的迭代器将是Iterator&lt;String&gt;,但随后它尝试访问第一个元素,即Integer,因此失败了。

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

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

现在循环实际上适用于第一个元素,因为第一个元素是String,我们可以看到输出;然后Iterator&lt;String&gt; 在第二个上失败,因为它不是字符串。

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

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

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

此修改也有效:

for (Object o : (List)list)

...大概是因为那时您处理的是Iterator,而不是Iterator&lt;String&gt;

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

这是我正在编译的文件:

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 for details.

下面是运行:

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:返回

}

同样,底线必须是:不要使用List&lt;String&gt; 与包含不是Strings 的内容的List 交互。 :-)

【讨论】:

  • 字符串部分在运行时丢失(擦除),因此实际上没有发生转换。但是像他那样做是非常错误的。
  • 我运行它并没有抛出异常:), 1.6.0_20, Windows XP
  • @Bozho:嗯...在 1.6.0_21 上编译(对我来说)插入了一个 checkcast &lt;java/lang/String&gt; 指令,这会导致异常。
  • 这是一个相当有趣的讨论,但我想我们都同意代码有什么问题以及必须修复什么:)
  • @Bozho: "...但我想我们都同意代码的问题和必须修复的问题" 完全。 :-)
【解决方案2】:

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

  • 使getObjects() 返回List&lt;Integer&gt;,而不是原始类型
  • 别指望List&lt;String&gt;,而是List&lt;Integer&gt;
  • 迭代时,循环for (Integer o : list)

【讨论】:

    【解决方案3】:

    问题是static List getObjects() { 方法返回一个通用(非参数化)List。然后将其分配给List&lt;String&gt;。这一行应该给出一个编译器警告,这应该是一个问题的信号。

    【讨论】:

      【解决方案4】:

      这部分:

      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的实现会在调用next()方法时尝试将Iterator发送的内容在String中进行转换。

      这就是您拥有 CCE 的原因。

      解决方案:

      要么在任何地方使用泛型,要么不使用它们,但保持一致非常重要。如果您返回了 List&lt;Integer&gt;List&lt;? super Integer&gt;,您会在编译时看到此问题。

      【讨论】:

      • 我想你的意思是Iterator&lt;String&gt;,而不是Iterator&lt;List&gt;
      猜你喜欢
      • 2021-05-28
      • 2011-09-13
      • 2012-05-08
      • 2012-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多