Java基础-泛型(泛型擦除)
文章目录
- 泛型擦除
- 泛型擦除的底层逻辑
- 代码示例
- 为什么需要泛型擦除?
- 泛型擦除带来的限制
- 桥接方法
- 示例:
- 编译器生成的桥接方法
- 桥接方法的作用
- 代码验证
- 拓展
泛型擦除
泛型擦除的底层逻辑
代码示例
class Box<T> {private T content;public void setContent(T content) {this.content = content;}public T getContent() {return content;}
}
擦除后的等价形式
编译器将 T 替换为 Object(因为没有指定边界):
class Box {private Object content;public void setContent(Object content) {this.content = content;}public Object getContent() {return content;}
}
在调用 getContent() 时,编译器会自动插入强制转换:
Box<String> box = new Box<>();
String content = (String) box.getContent(); // 隐含强制转换
为什么需要泛型擦除?
泛型擦除是 Java 为 向后兼容性(Backward Compatibility) 设计的关键机制,具体原因如下:
-
兼容旧版本代码
- Java 在 5.0 版本(2004年) 引入泛型。
- 泛型擦除让新代码(带泛型)和旧代码(无泛型)共享同一个类文件结构。例如:
// 泛型代码(Java 5+) List<String> list = new ArrayList<>(); // 非泛型代码(Java 1.4-) List rawList = new ArrayList();
泛型擦除后,两者本质都是 List,从而保证旧代码无需修改即可在新 JVM 上运行。
-
避免 JVM 修改
- 泛型擦除 无需修改 JVM 的底层机制。JVM 仍使用同样的字节码指令处理泛型和非泛型集合,极大降低了实现复杂度。
-
减少代码冗余
- 如果泛型类型不擦除,List 和 List 会分别生成不同的类文件,导致 代码膨胀(Code Bloat)。
- 擦除后,所有泛型实例共享同一个类文件,提升执行效率。
泛型擦除带来的限制
虽然泛型擦除带来兼容性,但也引入了一些限制:
- 无法使用原始类型参数
// 编译错误:不能使用基本类型作为泛型参数
List<int> list = new ArrayList<>();
- 运行时无法获取泛型类型信息
public void printType(List<String> list) {// 尝试通过反射获取泛型类型参数(无效)Type type = list.getClass().getGenericComponentType();System.out.println(type); // 输出: null(实际类型已被擦除)
}
- 无法创建泛型数组
// 编译错误:不能创建泛型数组
T[] array = new T[10];
桥接方法
示例:
父类与子类定义
// 泛型父类
public class Box<T> {private T value;public void set(T value) {this.value = value;}public T get() {return value;}
}// 子类指定具体类型参数为 String
public class StringBox extends Box<String> {@Overridepublic void set(String value) { // 重写父类方法,参数类型为 Stringsuper.set(value.toUpperCase());}@Overridepublic String get() { // 重写父类方法,返回类型为 Stringreturn super.get();}
}
问题
- 由于泛型擦除,Box 编译后的原始方法是:
- set(Object value)(原 set(String value))
- get() 的返回类型是 Object(原 String)
- StringBox 中的重写方法是 set(String) 和 get(): String,与父类擦除后的 set(Object) 和 get(): Object 方法签名不匹配。
这会导致多态性失效:通过 Box box = new StringBox() 调用 set 方法时,JVM 期望的签名是 set(Object),但 StringBox 只有 set(String)。
编译器生成的桥接方法
编译器会在 StringBox 类中插入两个桥接方法,确保方法重写和类型安全:
// (通过反编译字节码可见)
public class StringBox extends Box<String> {// 用户定义的显式重写@Overridepublic void set(String value) {super.set(value.toUpperCase());}@Overridepublic String get() {return super.get();}// ------------------------------------// 编译器生成的桥接方法(对用户不可见)// ------------------------------------// 桥接方法 1:兼容父类 `set(Object value)` public void set(Object value) {// 强制类型检查并调用实际方法 `set(String)` this.set((String) value);}// 桥接方法 2:兼容父类 `get()`public Object get() {// 调用实际方法 `get(): String` 并向上转型为 Objectreturn this.get();}
}
桥接方法的作用
- 保证方法重写的多态性
- JVM 通过方法签名(方法名 + 参数类型)和返回类型确定方法分派。
- 在泛型擦除后,父类与子类的方法签名不匹配,桥接方法充当中间层,确保父类引用能正确调用子类的具体方法。
- 强制类型安全
- 桥接方法中会插入类型检查:
public void set(Object value) {this.set((String) value); // 强制转换为 String }
- 若传入非法类型(如 Integer),运行时会抛出 ClassCastException,避免了类型混乱。
代码验证
通过反射查看 StringBox 的桥接方法:
import java.lang.reflect.Method;public class BridgeMethodDemo {public static void main(String[] args) {for (Method method : StringBox.class.getDeclaredMethods()) {if (method.isBridge()) {System.out.println("桥接方法:" + method);}}}
}
输出结果:
桥接方法:public void StringBox.set(java.lang.Object)
桥接方法:public java.lang.Object StringBox.get()
拓展
-
当通过父类类型调用 set(“hello”) 时,实际调用的是桥接方法 set(Object),它会转换类型并调用子类的 set(String)。
-
但 直接通过子类调用 set(String) 时,JVM 直接调用子类方法,无需桥接方法。
public class Main {public static void main(String[] args) throws Exception {// 场景1: 通过父类类型引用调用 set()Box<String> box = new StringBox();System.out.println("=== 通过父类调用 set() ===");box.set("hello"); // 调用实际上会经过桥接方法 set(Object)// 场景2: 直接通过子类类型调用 set()StringBox stringBox = new StringBox();System.out.println("\n=== 直接通过子类调用 set() ===");stringBox.set("world"); // 直接调用 set(String)} }
-
桥接方法的间接调用(父类引用场景)
- 当通过 Box box = new StringBox() 调用 box.set(“hello”) 时,Box 类在泛型擦除后实际的方法签名是 set(Object)。
- 桥接方法(子类中的 set(Object))会被触发,将参数强制转换为 String,然后调用子类的 set(String) 方法。
- 输出中看到 StringBox.set 被调用,证实桥接方法成功转发到子类具体方法。
-
直接调用子类方法(无需桥接)
- StringBox stringBox = new StringBox(); stringBox.set(“world”) 直接调用子类重写的 set(String) 方法。
- JVM 直接选择与方法签名 set(String) 精确匹配的方法,无需桥接方法。