Java 泛型的类型擦除是否会导致全类型强制转换?

2022-09-04 20:40:25

我知道当Java编译器编译泛型类型时,它会执行类型擦除并从代码中删除对泛型的所有引用,而my只是一个.ArrayList<Cheesecake>ArrayList

我没有明确答案的问题是,缺少类型(因此是强制性的类型转换)是否会导致速度变慢。换句话说,如果我使用Oracle的标准Java编译器和标准JVM 1.7:

  • 字节码是否包含类型转换?
  • 如果是,它是否包括运行时检查以检查它是否是正确的类型?
  • 转换本身是否花费了不平凡的时间?
  • 如果使我自己的类看起来与 相同,只是将所有的s都转换为s,我就会得到任何东西(再次只是一个假设,我对拥有更大的二进制文件的任何副作用不感兴趣,因为更多的类和我代码中的重复)。CheesecakeListArrayListObjectCheesecake

我对类型擦除导致的任何其他问题不感兴趣,只是来自比我更了解JVM的人的简单答案。

我最感兴趣的是这个例子中的成本:

List<Cheesecake> list = new ArrayList<Cheesecake>();
for (int i = 0; i < list.size(); i++)
{
   // I know this gets converted to ((Cheesecake)list.get(i)).at(me)
   list.get(i).eat(me); 
}

与以下情况相比,循环内的投射是否昂贵和/或重要:

CheesecakeList list = new CheesecakeList();
for (int i = 0; i < list.size(); i++)
{
   //where the return of list.get() is a Cheesecake, therefore there is no casting.
   list.get(i).eat(me); 
}

免责声明:这主要是学术好奇心的问题。我真的怀疑类型转换是否存在任何重大的性能问题,如果我在代码中发现性能错误,那么消除对它们的需求甚至不会是我会做的前5件事之一。如果您正在阅读本文,因为您确实遇到了性能问题,请帮自己一个忙,并启动一个分析器并找出瓶颈的位置。如果你真的相信它的类型转换,那么只有这样你才应该尝试以某种方式优化它。


答案 1

小故事:是的,有一个类型检查。这是证据 -

给定以下类:

// Let's define a generic class.
public class Cell<T> {
  public void set(T t) { this.t = t; }
  public T get() { return t; }
  
  private T t;
}

public class A {

  static Cell<String> cell = new Cell<String>();  // Instantiate it.
  
  public static void main(String[] args) {
    // Now, let's use it.
    cell.set("a");
    String s = cell.get(); 
    System.out.println(s);
  }  
}

编译成(通过 反编译)的字节码如下:A.main()javap -c A.class

public static void main(java.lang.String[]);
Code:
   0: getstatic     #20                 // Field cell:Lp2/Cell;
   3: ldc           #22                 // String a
   5: invokevirtual #24                 // Method p2/Cell.set:(Ljava/lang/Object;)V
   8: getstatic     #20                 // Field cell:Lp2/Cell;
  11: invokevirtual #30                 // Method p2/Cell.get:()Ljava/lang/Object;
  14: checkcast     #34                 // class java/lang/String
  17: astore_1      
  18: getstatic     #36                 // Field java/lang/System.out:Ljava/io/PrintStream;
  21: aload_1       
  22: invokevirtual #42                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  25: return        
}

正如你可以在 offset 中一样,对 的结果进行类型检查以验证它确实是一个字符串:14cell.get()

 14: checkcast     #34                 // class java/lang/String

这确实会产生一些运行时损失。但是,由于JVM得到了很好的优化,因此其影响可能微乎其微。

更长的故事:

您将如何实现这样的类?这个类难道不会定义一个数组来保存元素吗?请注意,对数组的每次赋值都会进行隐藏类型检查。因此,您不会像您想象的那么多(尽管您的程序可能会执行比写入操作更多的读取操作,因此数组会为您提供一些东西)。CheesecakeListCheesecake[]

底线:不要过早优化。

最后的评论。人们通常认为类型擦除意味着被编译成.那不是真的。擦除仅应用于泛型类/方法的定义。它不适用于这些类/方法的使用站点。Cell<String>Cell<Object>

换句话说,该类被编译为 ,就好像它被写成 .如果有上限(比如说),则将其编译为 。在代码中的其他地方,通常类型为类实例化的变量/参数(例如 ),不应用擦除。类型的事实保存在类文件中。这允许编译器正确键入检查程序。Cell<T>Cell<Object>TNumberCell<Number>CellCell<String> myCellmyCellCell<String>


答案 2

类型检查在编译时完成。如果这样做:

List<Cheesecake> list = new ArrayList<Cheesecake>();

那么泛型类型可以在编译时检查。这将擦除到:

List list = new ArrayList();

这与任何其他上演(例如)没有什么不同。Object o = new Integer(5);


推荐