10.泛型底层原理:类型擦除与边界限定

🧱 Java基础与集合篇 · 第10篇

主题:泛型底层原理:类型擦除与边界限定

🚀 高频指数:★★★★★
🎯 你将收获:Java 泛型的编译期机制、类型擦除原理、桥接方法生成、通配符与边界限定的本质。
💡 泛型是 Java 面试里最容易“说错话”的知识点之一。


一、为什么要问泛型?

💬 面试官想听到的不是“泛型是为了类型安全”,
而是——
你是否理解 Java 泛型是“伪泛型”,本质是编译期检查,运行期无泛型信息(擦除)。

Java 泛型不是 C++ 的模板,不会为泛型生成不同的类。
它只是让编译器去做类型检查,运行期仍然是原始类型。

☕️ 一句话:
泛型强在编译期,弱在运行期。


二、Java 泛型底层本质:类型擦除(Type Erasure)

Java 泛型在 编译期 起作用,
运行期 会被完全擦除。

示例:

List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();

运行期:

list1.getClass() == list2.getClass(); // true

解释:字节码里两者都是 List,泛型参数在运行期不存在。


三、类型擦除后是什么?

  1. 无界泛型 T 会被擦除为 Object
class Box<T> {
    T value;
}

擦除后:

class Box {
    Object value;
}
  1. 有界泛型 T extends Number 会被擦除为上界:
class Box<T extends Number> { ... }

擦除后等价于:

class Box {
    Number value;
}

结论:泛型擦除后会替换为上界或 Object。


四、为什么会出现桥接方法(Bridge Method)?

桥接方法用于“保持多态一致性”,
尤其是在 泛型与继承结合时

示例:

class Parent<T> {
    T get() { return null; }
}
class Child extends Parent<String> {
    @Override
    String get() { return "A"; }
}

擦除后,Parent 的方法为:

Object get();

而 Child 的 get() 为:

String get();

JVM 认为这是 “方法签名不一致”,
编译器自动生成桥接方法:

Object get() {
    return get(); // 调用 String get()
}

☕️ 一句话:桥接方法是擦除机制的副产物,用于维持重写关系。


五、通配符与边界限定真正含义(重要)

1️⃣ 无界通配符 ?

List<?> list

含义:

  • 你不知道列表里是什么类型;
  • 你几乎不能往里 add(除了 null);
  • 只能读,不能写。

“读多写少→用 ?”


2️⃣ 上界通配符 ? extends T

List<? extends Number>

含义:

  • 列表元素类型是 Number 或其子类;
  • get() 返回 Number
  • 不能 add 除了 null(因为不知道实际子类型)。

“只读不写→extends”
PECS 原则:Producer Extends


3️⃣ 下界通配符 ? super T

List<? super Integer>

含义:

  • 列表元素类型是 Integer 或其父类;
  • get() 返回 Object(可能是父类);
  • add(Integer) 一定安全。

“写多读少→super”
PECS 原则:Consumer Super


六、PECS 原则总结(面试必考)

PECS:Producer Extends, Consumer Super

要从中取数据(Producer),用 extends
要往里放数据(Consumer),用 super

背这个就够了。


七、泛型常见高级问题

❌ 为什么不能创建泛型数组?

List<String>[] lists = new List<String>[10]; // 编译错误

因为数组在运行期需要知道确切类型,而泛型已经擦除。


❌ 为什么下面代码会报警告?

List<String> list = new ArrayList();

原因:Raw Type(原始类型),失去了泛型检查,会产生类型安全问题。


❌ 重载方法能否根据泛型区分?

void test(List<String> list);
void test(List<Integer> list);

运行期擦除后签名相同,因此无法重载。


八、面试官追问清单

问题答案要点
Java 泛型是编译期还是运行期?编译期有效,运行期擦除。
类型擦除是什么?泛型信息被擦除成上界或 Object。
为什么要擦除?为了兼容旧版本 JVM(JDK1.4)。
什么是桥接方法?泛型导致重写签名冲突时,编译器生成中间方法保持多态。
PECS 是什么?Producer Extends, Consumer Super。
为什么不能创建泛型数组?数组需要运行期确定类型,而泛型已经擦除。

九、项目实践建议

  • 开发中优先使用:
    List<? extends T> 表示“从中取数据”
    List<? super T> 表示“往里放数据”
  • 尽量避免原始类型(Raw Type);
  • 避免复杂嵌套泛型,使用自定义类或 record 简化;
  • 泛型接口/泛型方法更灵活,利于复用。

十、口诀记忆

☕️ “泛型编译用,运行全擦空;读用 extends,写用 super。”


十一、小结

知识点结论
泛型本质编译期类型检查,运行期擦除
擦除后替换无界→Object,有界→上界类型
桥接方法为保持重写关系自动生成
通配符? 读;? extends 强读;? super 强写
PECS 原则Producer Extends, Consumer Super
原始类型不安全,避免使用

✅ 一句话总结:
“泛型是编译器的语言,不是 JVM 的语言。”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愤怒的代码

如果您有受益,欢迎打赏博主😊

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值