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。
类型擦除的规则:
- 无边界泛型:泛型类型会被替换为
Object
。class Box<T> { private T value; } // 编译后等价于: class Box { private Object value; }
- 有边界泛型:如果定义了泛型边界(
<T extends Number>
),类型擦除时会替换为边界类型。class Box<T extends Number> { private T value; } // 编译后等价于: class Box { private Number value; }
- 桥接方法:在泛型类中,子类与父类方法签名可能不一致,编译器会生成桥接方法以保证兼容性。
示例:
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 泛型在运行时有以下局限性:
- 不能使用泛型类型创建实例:
T value = new T(); // 编译错误
- 无法检测运行时类型: 泛型类型的实际参数在运行时不可用。
if (value instanceof T) { // 编译错误 // 泛型信息在运行时已经被擦除 }
- 不能创建泛型数组:
T[] array = new T[10]; // 编译错误
- 类型冲突: 方法签名可能因为类型擦除而发生冲突。
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()); } }
总结
- 泛型的优点:编译时类型检查、提高代码重用性。
- 类型擦除的影响:导致一些运行时局限性(如无法检测泛型类型、泛型数组不能直接创建)。
- 实际开发建议:尽量使用泛型的能力,尤其是集合类(如
List
和Map
),以提高代码的可读性和安全性。