【问题标题】:Understanding typesafety anomaly with Java generics [duplicate]使用 Java 泛型理解类型安全异常 [重复]
【发布时间】:2015-07-01 21:42:18
【问题描述】:

我有以下代码:

public static void main(String[] args) {
    List<String> s = new ArrayList<String>();
    s.add("kshitiz");

    //This is not typesafe. It should blow up at runtime
    List<Integer> i = new ArrayList(s);
    System.out.println(i.get(0));
}

这个程序运行良好,它打印kshitiz。仅当我将最后一行替换为:

System.out.println(i.get(0).getClass());

例外:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

这里发生了什么?

【问题讨论】:

  • 请指定Java版本
  • 抛出什么异常?
  • Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
  • 您忽略了一些重要的编译器警告。您的 IDE 或编译器应该警告您访问原始类型。

标签: java generics java-8


【解决方案1】:

我猜你知道,泛型类型在运行时消失了。现在,要看看幕后发生了什么,让我们看看这段代码

public static Class<?> getClass(final Object object) {
    return object.getClass();
}

public static void main(final String[] args) {
    final List<String> s = new ArrayList<String>();
    s.add("kshitiz");

    // This is not typesafe. It should blow up at runtime
    final List<Integer> i = new ArrayList(s);
    System.out.println(getClass(i.get(0)));
    System.out.println(i.get(0).getClass());
}

以及javap -c的输出

  [...]
  26: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
  29: aload_2
  30: iconst_0
  31: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
  36: invokestatic  #10                 // Method getClass:(Ljava/lang/Object;)Ljava/lang/Class;
  39: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  42: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
  45: aload_2
  46: iconst_0
  47: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
  52: checkcast     #12                 // class java/lang/Integer
  55: invokevirtual #2                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
  58: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  61: return

所以你看到,在第二次调用中,String 被强制转换为 Integer,而在第一种情况下,它被视为一个对象。 因此,只要像处理任何其他对象一样处理 String,一切都很好,但是一旦您调用列表元素类型的方法,该对象就会被强制转换为该类型。

【讨论】:

  • 有趣的是为什么编译器会进行这种不必要的强制转换。
【解决方案2】:

Java 中的泛型被设计破坏了。

在您的List&lt;Integer&gt; i = new ArrayList(s); 中,您有一个原始的ArrayList。这意味着您忽略类型参数,它的工作方式就像它是Object。然后你(隐式地)将ArrayList 转换为List&lt;Integer&gt;,但在运行时不会检查泛型,因此 JVM 不关心不匹配。

当您执行 System.out.println(i.get(0)) 时,您不会将 String 转换为 Integer,因为调用了 'println(Object)` 方法。

System.out.println(i.get(0).getClass()) 中,这个神奇的刹车因为某种原因(你可能可以在规范中找到这个原因,但我建议你不要这样做)你的String 被转换为Integer

【讨论】:

  • 您可以讨论如果代码在将ArrayList 分配给ArrayList&lt;Integer&gt; 或调用get 时失败是否更好,但是,我没有看到足够的区别有理由称当前行为“被设计破坏”。毕竟结果是一样的,忽略编译时警告会导致运行时异常……
猜你喜欢
  • 2017-11-06
  • 1970-01-01
  • 2012-04-20
  • 2014-01-12
  • 2013-05-17
  • 2021-03-27
  • 2010-10-26
  • 2019-12-18
  • 2012-02-06
相关资源
最近更新 更多