🧱 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,泛型参数在运行期不存在。
三、类型擦除后是什么?
- 无界泛型 T 会被擦除为
Object:
class Box<T> {
T value;
}
擦除后:
class Box {
Object value;
}
- 有界泛型 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 的语言。”
3万+

被折叠的 条评论
为什么被折叠?



