1. 泛型真的存在吗?
在 Java 中,泛型看起来像是一个独立的类型系统,但实际上它只是一种 编译时 的机制。Java 为了向后兼容,在 运行时 其实并没有真正的泛型类型,所有的泛型信息都会被 擦除,这就是所谓的“类型擦除”机制。
举个简单的例子,我们定义一个泛型类:
public abstract class A<T> {
abstract T test(T t);
}
在编译后,Java 其实会把 T
替换成 Object,也就是说,代码实际上变成了:
public abstract class A {
abstract Object test(Object t); // 默认就是 Object
}
所以本质上,泛型只是编译器帮你做了 类型检查,但到了运行时,所有泛型信息都会消失。
2. 泛型的类型擦除规则
Java 的类型擦除不仅仅是简单地变成 Object
,如果泛型定义了 上界,那么擦除后的类型就是上界的类型。例如:
public abstract class A<T extends Number> {
abstract T test(T t);
}
编译后会变成:
public abstract class A {
abstract Number test(Number t); // 上界是 Number
}
这里 T
被擦除成了 Number
,因为它的上界被指定为 Number
,所以运行时的 A
其实就像一个普通的 Number 类型的类。
3. 类型擦除的运行时影响
由于 Java 运行时并不会真的保留泛型信息,所以有些操作会受到限制。例如,泛型类型不会真的存在于 字节码 中,这导致我们可以在使用泛型时,不指定具体的类型:
public static void main(String[] args) {
Test test = new Test(); // 没有指定类型,默认就是原始类型
}
虽然可以这样写,但编译器会 警告,提示你最好指定类型。
(1) 强制类型转换
因为类型擦除,Java 需要用 强制类型转换 来保证泛型的正确性。比如:
A<String> a = new B();
String i = a.test("10"); // 编译时看着是 String,但实际上是 Object
编译后实际的代码变成了:
A a = new B();
String i = (String) a.test("10"); // 运行时依靠强制类型转换
这里 a.test("10")
返回的是 Object
,但我们仍然能赋值给 String
,因为编译器 自动加上了强制类型转换。但这也带来了一个问题:如果你不小心传入了错误的类型,就会在 运行时 发生 ClassCastException
。
(2) 桥接方法(Bridge Method)
另一个影响是 桥接方法(Bridge Method)。
我们知道,重写方法时,方法的返回类型和参数类型必须一致。但泛型擦除后,父类的方法类型变成了 Object
,子类方法可能是 String
,按理说是不满足重写条件的。
例如:
public class B extends A<String> {
@Override
String test(String s) {
return null;
}
}
编译后的代码实际上会 自动生成一个桥接方法,让重写成立:
public class B extends A {
public Object test(Object obj) { // 这才是真正重写的方法
return this.test((String) obj); // 桥接方法调用我们自己写的方法
}
public String test(String str) { // 这个是我们写的方法
return null;
}
}
所以 @Override
不会报错,是因为编译器 偷偷帮我们加了桥接方法。
4. 类型擦除带来的限制
由于类型擦除,Java 的泛型有一些 限制,可能会让你感到意外。
(1) 不能用泛型进行 instanceof
判断
因为泛型类型在运行时是被擦除的,所以 instanceof
不能判断具体的泛型类型。例如:
Test<String> test = new Test<>();
System.out.println(test instanceof Test<String>); // ❌ 错误
编译时会报错,正确的写法只能是:
System.out.println(test instanceof Test); // ✅ 只能判断原始类型
(2) 不能创建泛型数组
由于 Java 运行时不会存储泛型信息,所以不能直接创建泛型数组。例如:
Test<String>[] testArray = new Test<String>[10]; // ❌ 编译错误
正确的方式是使用原始类型数组:
Test[] testArray = new Test[10]; // ✅ 只能创建原始类型的数组
5. 总结
Java 的泛型其实是 编译时检查 的,运行时并不会真的保留泛型信息,这就是 类型擦除 机制。主要影响如下:
- 所有泛型信息在编译后都会被擦除,变成
Object
或上界类型。 - Java 运行时依靠强制类型转换保证泛型的正确性。
- 泛型方法重写时,编译器会自动生成桥接方法。
- 不能用
instanceof
直接判断泛型类型。 - 不能创建泛型数组。
虽然类型擦除让 Java 的泛型功能有所限制,但它也保证了 向下兼容性,让 Java 既能使用泛型,又能兼容老版本代码。因此,理解类型擦除的原理,可以帮助我们更好地编写安全、稳定的泛型代码!