Java 的泛型是如何工作的 ? 什么是类型擦除 ?

参考回答

Java 的泛型通过在编译时提供类型检查和保证类型安全来工作。在运行时,由于 Java 的泛型实现采用了 类型擦除,泛型信息会被移除,所有泛型类型最终都会被替换为原生类型(如 Object 或具体的边界类型)。类型擦除是为了兼容 Java 的旧版本(JDK 1.4 及之前),那些版本没有泛型支持。


详细讲解与拓展

1. 泛型是如何工作的

泛型允许你在类、接口和方法中定义类型参数,以便重用代码并确保类型安全。以下是泛型的几个关键点:

  • 编译时检查:泛型会在编译时检查类型是否匹配,避免了运行时出现 ClassCastException
  • 类型参数:使用尖括号 <T> 定义类型参数。泛型类、方法或接口可以适配多种类型。
  • 工作原理:编译器根据泛型代码生成类型安全的原生代码,然后通过 类型擦除 在运行时将泛型类型替换为它的边界(或 Object)。

示例代码:

// 泛型类
class Box<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}

public class GenericExample {
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>();
        stringBox.set("Hello");
        System.out.println(stringBox.get()); // 输出:Hello
    }
}

在上面的例子中,Box 是一个泛型类,可以存储任意类型的值。<T> 是类型参数,使用时会替换为具体的类型(如 String)。


2. 什么是类型擦除

类型擦除是指 Java 的泛型只存在于编译期,编译后会将泛型类型擦除,转变为原生类型(raw type),以兼容旧版本的 JVM。

类型擦除的规则:

  1. 无边界泛型:泛型类型会被替换为 Object
    class Box<T> {
       private T value;
    }
    // 编译后等价于:
    class Box {
       private Object value;
    }
    
  2. 有边界泛型:如果定义了泛型边界(<T extends Number>),类型擦除时会替换为边界类型。
    class Box<T extends Number> {
       private T value;
    }
    // 编译后等价于:
    class Box {
       private Number value;
    }
    
  3. 桥接方法:在泛型类中,子类与父类方法签名可能不一致,编译器会生成桥接方法以保证兼容性。

示例:

class MyNumberBox<T extends Number> {
    public T add(T a, T b) {
        return a; // 伪代码,仅示意
    }
}

编译后,泛型方法 add(T, T) 会被转换为:

public Number add(Number a, Number b) {
    return a;
}

3. 类型擦除的限制

由于类型擦除的存在,Java 泛型在运行时有以下局限性:

  1. 不能使用泛型类型创建实例
    T value = new T(); // 编译错误
    
  2. 无法检测运行时类型: 泛型类型的实际参数在运行时不可用。
    if (value instanceof T) {  // 编译错误
       // 泛型信息在运行时已经被擦除
    }
    
  3. 不能创建泛型数组
    T[] array = new T[10]; // 编译错误
    
  4. 类型冲突: 方法签名可能因为类型擦除而发生冲突。
    class Example<T> {
       public void method(List<String> list) {}
       public void method(List<Integer> list) {} // 编译错误:类型擦除后方法签名相同
    }
    

4. 泛型的拓展知识

  • 通配符(Wildcard): 泛型中可以使用通配符 ? 表示不确定的类型。常用的通配符有:
    • ? extends T:表示该类型是 T 的子类或 T 本身。
    • ? super T:表示该类型是 T 的父类或 T 本身。
    • ?:表示不确定的类型。

    示例:

    public static void printList(List<? extends Number> list) {
      for (Number n : list) {
          System.out.println(n);
      }
    }
    
  • 泛型方法: 可以在方法中单独声明泛型类型。
    public static <T> void printArray(T[] array) {
      for (T element : array) {
          System.out.println(element);
      }
    }
    
  • 泛型擦除的绕过方式
    • 使用反射获取泛型类型:
    class GenericClass<T> {
        public void printType() {
            System.out.println(this.getClass().getGenericSuperclass());
        }
    }
    

总结

  • 泛型的优点:编译时类型检查、提高代码重用性。
  • 类型擦除的影响:导致一些运行时局限性(如无法检测泛型类型、泛型数组不能直接创建)。
  • 实际开发建议:尽量使用泛型的能力,尤其是集合类(如 ListMap),以提高代码的可读性和安全性。

发表回复

后才能评论