📜  Java中的类型擦除(1)

📅  最后修改于: 2023-12-03 15:16:34.172000             🧑  作者: Mango

Java中的类型擦除

在Java中,泛型是一种通过参数化类型来实现更强类型安全的机制。具体来说,泛型允许在类或方法定义中使用类型形参(type parameters),然后在实例化时通过指定具体的类型来替代这些形参。例如:

class List<T> {
  private T[] elements;
  
  public List(T[] elements) {
    this.elements = elements;
  }
  
  public T get(int index) {
    return elements[index];
  }
  
  public void set(int index, T value) {
    elements[index] = value;
  }
}

List<String> list = new List<>(new String[]{"foo", "bar", "baz"});
String s = list.get(0);
list.set(1, "qux");

在这个例子中,我们定义了一个泛型类List<T>,其中T是类型形参。我们可以通过这个类来创建一个具体类型为List<String>的实例。由于类型参数T被指定为String,因此对于List<String>实例,其get()方法返回的是一个String对象,而不是一个Object对象,而且该类中的所有其它方法也都能够正确地与String类型进行交互,从而避免了一些类型错误。

然而,当Java代码被编译器编译成字节码后,类型擦除(type erasure)就会被应用。在类型擦除的过程中,Java编译器会将所有泛型类型转换为其原始类型(raw type),从而优化程序的性能。具体来说,泛型类型参数会被擦除掉,而在代码生成时会插入一些转换代码来确保类型的安全性。

例如:

class List {
  private Object[] elements;
  
  public List(Object[] elements) {
    this.elements = elements;
  }
  
  public Object get(int index) {
    return elements[index];
  }
  
  public void set(int index, Object value) {
    elements[index] = value;
  }
}

List list = new List(new String[]{"foo", "bar", "baz"});
String s = (String) list.get(0);
list.set(1, "qux");

在这个例子中,我们看到了使用了类型擦除的List类的原始形态。由于类型形参T被擦除掉了,因此在实例化时我们不再需要指定具体的类型。在List类中,我们使用了一个Object数组来存储元素,而不是使用一个泛型类型的数组。同时,get()set()方法的返回类型和参数类型也被变成了Object类型,因此在使用时需要手动进行类型转换。

需要注意的是,由于类型擦除的存在,Java中的泛型并不能完全避免类型错误。例如:

List<String> list = new List<>(new String[]{"foo", "bar", "baz"});
List<Object> objList = list; // 编译错误

在这个例子中,我们定义了一个List<String>类型的实例,然后试图将它赋值给一个List<Object>类型的变量。由于类型擦除,两者的具体类型都变成了List,因此看起来这个赋值应该是合法的。但实际上这是一个编译错误,因为在类型擦除后,List<String>List<Object>都变成了List类型,而Java中是不允许将一个具有某个泛型类型参数的对象赋值给具有其它泛型类型参数的对象的。

总之,在Java中使用泛型可以提高程序的类型安全性和代码的可读性,但需要注意类型擦除的存在以及可能产生的类型错误问题。