6

我正在阅读有关 C++ 模板和 C# 泛型的讨论,以及它们与 Java 的类型擦除泛型有何不同。我读过一个声明,说 Java 在运行时仍然使用强制转换,例如在处理集合时。如果这是真的,我不知道!

假设我有如下代码:

ArrayList<SomeClass> list = new ArrayList<SomeClass>();
...
SomeClass object = list.get(0);

我的问题是。这是否有效地编译为

ArrayList list = new ArrayList();
...
SomeClass object = (SomeClass) list.get(0);

如果是这样,为什么?我认为 list 是类型ArrayList<SomeClass>保证的事实,在编译时和运行时,只有 SomeClass 将存储在 ArrayList 中?或者您是否可以进行不安全的类型转换以将 aArrayList<OtherClass>转换为ArrayList<SomeClass>

在 Java 泛型中是否存在其他运行时类型转换的情况?

最后,如果确实使用了运行时强制转换,是否存在 JIT 可以忽略运行时强制转换检查的情况?

(请不要回答/评论微优化不值得,抢先优化是万恶之源等。我在其他类似问题上看到了这些。这些点很好理解,但它们并没有带走尝试的意义了解类型擦除的泛型是如何在底层实现的。)

4

5 回答 5

5

你的假设是正确的。

类型检查始终是必要的,因为您可以编写以下合法代码:

ArrayList<X> good = new ArrayList<X>();
ArrayList q = x;
ArrayList<Y> evil = (ArrayList<Y>)q;  //Doesn't throw due to type erasure
evil.add(new Y()); //this will actually succeed
X boom = good.get(0);

转换from ArrayListtoArrayList<Y>将(总是)给出未经检查的转换警告,但会(也总是)在运行时成功。

于 2012-04-12T15:03:54.837 回答
5

这是我写的一个小程序:

public class Test<T> {

  public T contents;

  public Test(T item) {
    contents = item;
  }

  public static void main(String[] args) {

    Test<String> t = new Test<String>("hello");
    System.out.println(t.contents);

  }

}

尝试用 编译它javac,然后用 . 查看字节码javap -verbose。我选择了一些有趣的行:

public java.lang.Object contents;

这应该在 Test 构造函数的定义之前出现。在示例代码中它是 T 类型,现在它是一个对象。那是擦除。

现在,查看主要方法:

public static void main(java.lang.String[]);
Code:
   Stack=3, Locals=2, Args_size=1
   0:   new #3; //class Test
   3:   dup
   4:   ldc #4; //String hello
   6:   invokespecial   #5; //Method "<init>":(Ljava/lang/Object;)V
   9:   astore_1
   10:  getstatic   #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   13:  aload_1
   14:  getfield    #2; //Field contents:Ljava/lang/Object;
   17:  checkcast   #7; //class java/lang/String
   20:  invokevirtual   #8; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   23:  return

然后我们可以在第 17 行看到checkcast命令,就在 println 之前——这是 Java 转换Object为已擦除泛型类型的地方——String

于 2012-04-12T15:12:06.953 回答
1

请记住,在运行时, anArrayList<String>与 an 相同,ArrayList并且使用底层实现Object[]。JIT 通常足够聪明,可以忽略强制转换,是的,但是由于泛型在运行时被删除,所以强制转换是必要的。

于 2012-04-12T15:04:52.017 回答
1

Java 在保持其版本之间的兼容性方面做得很好,这意味着 Java5+ 语法可以编译成 1.4 兼容的类。我想这是 ArrayList在运行时保证只有 SomeClass 会存储在 ArrayList 中的部分原因。

要查看您的泛型代码真正编译成什么,请使用出色的JAD工具。

你的代码确实编译成SomeClass object = (SomeClass) list.get(0);

于 2012-04-12T15:10:14.027 回答
1

我认为 list 是 ArrayList 类型的事实保证,在编译时和运行时,只有 SomeClass 将存储在 ArrayList 中?或者您是否可以进行不安全的类型转换以将 ArrayList 转换为 ArrayList?

是的,类型信息在运行时被丢弃。

这意味着您无法理解您是否有一个类型ArrayList<String>的对象或一个类型的对象ArrayList<Long>。JVM 不知道泛型类实例的实际类型参数,因此您没有运行时检查(并且只有在使用该数据时才会看到错误)。

此代码有效:

public void enqueueItem(ArrayList<Integer> list, Integer item) {
    List listAlias = list;
    listAlias.add(x.toString());
}

这在运行时不是问题,类型检查仍然可以帮助您避免任何问题,并且您可以将旧代码与新代码互操作。即使在 .NET 环境中,您也有相同的行为(IList<T>派生自IList)。

您所支付的只是强制转换对性能的影响(这是与 Java 的通用实现的最大区别)。或多或少的泛型是检测错误并使代码更具可读性的编译时助手。例如,这段代码对编译器非常有效:

public ArrayList getCollection() {
   return new ArrayList<Integer>();
}

public void doStuff() {
   ArrayList<String> list = getCollection();
   list.add("text");
}

在 Java 泛型中是否存在其他运行时类型转换的情况?

是的,每次你使用它们时(如果你对泛型做了坏事,你就会得到一个错误)。

最后,如果确实使用了运行时强制转换,是否存在 JIT 可以忽略运行时强制转换检查的情况?

认为不,它总是进行强制转换,但无论如何这是一个非常实现的细节,它跟踪子类的类型参数,所以如果你从泛型类派生你自己的类,那么你将从这个问题中解脱出来。

如果是这样,为什么?

即使您更改了某些接口,所有现有代码都将适用于新的泛型。这意味着您可以逐步更新到泛型。我不担心性能影响(我们用它生存了很长时间,现在它是一个问题吗?)但我不喜欢所有这些技巧。也许,毕竟,我们不能让橡木满了,妻子喝了……

于 2012-04-12T15:48:18.757 回答