Java泛型类型擦除

时间:2020-01-09 10:35:18  来源:igfitidea点击:

使用Java泛型,我们可以编写泛型程序,并且还可以在编译时提供更严格的类型检查,但是这些泛型类型仅保留在源代码级别。编译源代码时,所有通用类型参数都将被删除,此过程在Java通用中称为类型擦除。

类型擦除如何工作

Java中的类型擦除操作如下:

  • 如果未指定显式绑定类型,则将泛型类型中的所有类型参数替换为它们的绑定类型,然后用Object替换泛型类型参数。因此,生成的字节码仅包含普通的类,接口和方法,所有通用参数均被实际类型替换。

  • 必要时插入类型转换,以保持类型安全。

  • 生成桥接方法以在扩展的泛型类型中保留多态。

通用类中的类型擦除

考虑以下具有泛型类型参数T的泛型类。

public class GenericClass<T> {
  T obj;
  GenericClass(T obj){
    this.obj = obj;
  }
  public T getObj() {
    return obj;
  } 
}

由于类型参数T是无界的,因此Java编译器将其替换为Object,并且在编译类之后看起来像

public class GenericClass {
  Object obj;
  GenericClass(Object obj){
    this.obj = obj;
  }
  public Object getObj() {
    return obj;
  } 
}

考虑另一个带有有限类型参数的泛型类。

public class GenericClass<T extends String> {
  T obj;
  GenericClass(T obj){
    this.obj = obj;
  }
  public T getObj() {
    return obj;
  } 
}

由于类型参数T是有界的,因此Java编译器会用绑定类String替换它,并且在编译类看起来像这样之后

public class GenericClass {
  String obj;
  GenericClass(String obj){
    this.obj = obj;
  }
  public String getObj() {
    return obj;
  } 
}

通用方法中的类型擦除

Java编译器还会擦除通用方法参数中的类型参数。考虑以下通用方法,该方法对传递的数组中传递的元素的出现次数进行计数。

public static <T> int count(T[] numberArray, T elem) {
  int cnt = 0;
  for (T e : numberArray){
    if (e.equals(elem))
      ++cnt;
  }
  return cnt;
}

由于T是无界的,因此Java编译器将其替换为Object,并且编译后的方法看起来像

public static int count(Object[] numberArray, Object elem) {
  int cnt = 0;
  for (Object e : numberArray){
    if (e.equals(elem))
      ++cnt;
  }
  return cnt;
}

类型擦除和桥接方法

有时,作为类型擦除过程的一部分,编译器会创建一种综合方法,称为桥接方法。考虑以下类,以查看Java中的bridge方法的示例。

public class GenClass<T> {
  T obj;
  public GenClass(T obj) { 
    this.obj = obj; 
  }
  public void setObj(T obj) {
    this.obj = obj;
  }  
}

然后将此GenClass扩展为另一个类,如下所示:

public class MyClass extends GenClass {
  public MyClass(Integer data) { 
    super(data); 
  } 
  public void setObj(Integer data) {
    System.out.println("MyClass.setData");
    super.setObj(data);
  } 
}

这里的目的是重写子类中的父类setObj()方法。类型擦除后,GenClass和MyClass类变为

public class GenClass {
  Object obj;
  public GenClass(Object obj) { 
    this.obj = obj; 
  }
  public void setObj(Object obj) {
    this.obj = obj;
  }  
}
public class MyClass extends GenClass {
  public MyClass(Integer data) { 
    super(data); 
  } 
  public void setObj(Integer data) {
    System.out.println("MyClass.setData");
    super.setObj(data);
  } 
}

类型擦除后,方法签名不匹配。 GenClass方法变为setObj(Object obj),而MyClass方法变为setObj(Integer data)。因此,GenClass setObj方法不会覆盖MyClass setObj方法。

为了解决此问题并在类型擦除后保留通用类型的多态性,Java编译器生成了一个桥接方法,以确保子类型可以按预期工作。对于MyClass类,编译器为setObj()生成以下桥接方法。

public class MyClass extends GenClass {
  public MyClass(Integer data) { 
    super(data); 
  } 
  // Bridge method generated by the compiler
  public void setObj(Object data) {
    setObj((Integer) data);
  }
  public void setObj(Integer data) {
    System.out.println("MyClass.setData");
    super.setObj(data);
  } 
}

如我们所见,bridge方法具有与GenClass的setObj()方法相同的方法签名,并且它委托给原始的setObj()方法。