当前位置: 首页 > news >正文

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) 设计的关键机制,具体原因如下:

  1. 兼容旧版本代码

    • Java 在 5.0 版本(2004年) 引入泛型。
    • 泛型擦除让新代码(带泛型)和旧代码(无泛型)共享同一个类文件结构。例如:
    // 泛型代码(Java 5+)
    List<String> list = new ArrayList<>();
    // 非泛型代码(Java 1.4-)
    List rawList = new ArrayList();
    

    泛型擦除后,两者本质都是 List,从而保证旧代码无需修改即可在新 JVM 上运行。

  2. 避免 JVM 修改

    • 泛型擦除 无需修改 JVM 的底层机制。JVM 仍使用同样的字节码指令处理泛型和非泛型集合,极大降低了实现复杂度。
  3. 减少代码冗余

    • 如果泛型类型不擦除,List 和 List 会分别生成不同的类文件,导致 代码膨胀(Code Bloat)。
    • 擦除后,所有泛型实例共享同一个类文件,提升执行效率。

泛型擦除带来的限制

虽然泛型擦除带来兼容性,但也引入了一些限制:

  1. 无法使用原始类型参数
// 编译错误:不能使用基本类型作为泛型参数
List<int> list = new ArrayList<>();
  1. 运行时无法获取泛型类型信息
public void printType(List<String> list) {// 尝试通过反射获取泛型类型参数(无效)Type type = list.getClass().getGenericComponentType();System.out.println(type); // 输出: null(实际类型已被擦除)
}
  1. 无法创建泛型数组
// 编译错误:不能创建泛型数组
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();}
}

问题

  1. 由于泛型擦除,Box 编译后的原始方法是:
    • set(Object value)(原 set(String value))
    • get() 的返回类型是 Object(原 String)
  2. 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();}
}

桥接方法的作用

  1. 保证方法重写的多态性
  • JVM 通过方法签名(方法名 + 参数类型)和返回类型确定方法分派。
  • 在泛型擦除后,父类与子类的方法签名不匹配,桥接方法充当中间层,确保父类引用能正确调用子类的具体方法。
  1. 强制类型安全
  • 桥接方法中会插入类型检查:
    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)}
    }
    
  1. 桥接方法的间接调用(父类引用场景)

    • 当通过 Box box = new StringBox() 调用 box.set(“hello”) 时,Box 类在泛型擦除后实际的方法签名是 set(Object)。
    • 桥接方法(子类中的 set(Object))会被触发,将参数强制转换为 String,然后调用子类的 set(String) 方法。
    • 输出中看到 StringBox.set 被调用,证实桥接方法成功转发到子类具体方法。
  2. 直接调用子类方法(无需桥接)

    • StringBox stringBox = new StringBox(); stringBox.set(“world”) 直接调用子类重写的 set(String) 方法。
    • JVM 直接选择与方法签名 set(String) 精确匹配的方法,无需桥接方法。

相关文章:

  • 【redis】初识redis
  • 实验二.单按键控制LED
  • 自定义Jackson序列化和反序列化
  • 家用发电机的原理是什么?
  • 【STM32】在FreeRTOS下使用硬件SPI收发数据出现的时序耦合问题(WK2124芯片为例)
  • 使用 Sass 打造动态星空背景效果
  • 远方游子的归家记:模仿美食网页的制作与实现
  • React JSX?
  • C++红黑树
  • 时间的重构:科技如何重塑人类的时间感知与存在方式
  • 【大模型系列篇】深度研究智能体技术演进:从DeepResearch到DeepResearcher,如何重构AI研究范式
  • 深度访谈:数据中台的本质不是技术堆砌,而是业务引擎的重构
  • c++进阶-继承01
  • 加固笔记本:无人机领域智能作业的可靠算力中枢
  • 交易模式革新:Eagle Trader APP上线,助力自营交易考试效率提升
  • 区块链技术在数据隐私保护中的应用:从去中心化到零知识证明
  • 【Java】面向对象程序三板斧——如何优雅设计包、封装数据与优化代码块?
  • Spring Boot 微服务中集成 MyBatis-Plus 与集成原生 MyBatis 有哪些配置上的不同?
  • java开发中的设计模式之单例模式
  • 现代c++获取linux系统架构
  • 印巴冲突升级,巴基斯坦股市重挫7.29%,创5年来最大单日跌幅
  • 北京:下调个人住房公积金贷款利率
  • 全球第七个迪士尼主题公园将落户阿布扎比
  • 古龙逝世四十周年|中国武侠文学学会与多所高校联合发起学术纪念活动
  • 马克思主义理论研究教学名师系列访谈|鲍金:给予学生一碗水、自己就要有一桶水
  • 金融监管总局:正在修订并购贷款管理办法,将进一步释放并购贷款的潜力