hello,这篇文章,是我近期再学习Java 基础,根据自己的理解总结所得,意在系统化、全局化、结构化学过的知识。这篇文章将系统化介绍泛型,下图是本篇文章的框架图,通篇会围绕此图展开。

一、泛型的概念
1. 定义与词源
- 英文:
Generics - 中文「泛」字溯源:
- 本义:水满漫出、向外扩散、漂浮延展。
- 词义演化:水流漫延扩散 → 范围广阔普遍 → 分散不聚焦(浅表笼统)→ 包容多类、不限单一。
- 在「泛型」中的含义:通用、广谱、不局限于某一种固定类型,可适配多种类型。
- 泛型的完整定义:泛型是 JDK 5 引入的参数化类型机制,允许在类、接口、方法中定义类型占位符,使用时再指定具体类型,实现一套模板适配多种数据类型。
2. 5W2H 解析
What ------ 泛型是什么
泛型是 JDK5 引入的参数化类型 设计,允许在类、接口、方法上定义类型占位符 ,使用时再传入具体约束类型;核心是:类型参数化,编译期做类型约束。
Why ------ 为什么需要泛型
- 解决早期原始类型
List无类型限制、随意存对象的问题; - 编译期强类型检查,提前规避强制类型转换异常;
- 消除重复代码,一套容器 / 工具方法适配多种数据类型;
- 提升代码可读性,直观标识集合、方法的存储与操作类型。
When ------ 何时使用泛型
- 定义通用容器:List、Set、Map 等集合类;
- 编写通用工具方法、通用父类 / 公共接口;
- 需要统一处理多类型、且要求类型安全的业务场景;
- 方法返回值 / 入参需要动态绑定不确定类型时。
Where ------ 泛型作用范围
- 作用位置:类、接口、成员方法、静态方法;
- 生效阶段:仅编译期生效,负责语法校验与类型推断;
- 失效阶段:运行时因泛型擦除,尖括号内类型信息丢失;
- 边界范围:支持固定类型、上界限定、下界限定、无界通配符。
Who ------ 泛型由谁定义与使用
- 设计者:Java 官方 JDK5 版本新增语法规范;
- 执行者:javac 编译器负责泛型校验、类型推断、自动擦除;
- 使用者:业务开发人员,在集合、通用工具类中日常编写使用。
How ------ 泛型如何实现 & 分类使用
实现方式:通过 <类型占位符> 语法声明,编译期校验,编译后执行泛型擦除。
How Much ------ 泛型带来的影响与代价
正向收益
- 杜绝类型强转、代码更简洁、类型安全、复用性强;
- 配合 PECS 原则,精准控制读写权限,提升代码健壮性。
隐性代价
- 运行时存在泛型擦除,丢失容器泛型标记;
- 编译器需额外生成桥方法,作为擦除后的多态适配
- 衍生语法限制:无法直接 new T、泛型数组、泛型 instanceof 判断;(下篇文章扩展)
3.6 种常见表示形式(按约束从强到弱)
结合读写特性,可分为两类:
| 表示形式 | 读写特性 | 说明 |
|---|---|---|
List<String> |
可读可写 | 最具体、约束最强,明确限定为 String 类型 |
List<T> |
可读可写 | 定义泛型类 / 方法时使用,代表 "确定的某个类型" |
List<? super Fruit> |
可读(Object)、可写(Fruit 及子类) | 下界通配符,用于 "存数据" 的场景 |
List<? extends Fruit> |
可读(Fruit)、不可写 | 上界通配符,用于 "取数据" 的场景 |
List<?> |
可读(Object)、不可写 | 无界通配符,可接收任意泛型,只读不写 |
List(原始类型) |
可读可写(无安全检查) | 兼容老版本,无泛型约束,不推荐使用 |
二、泛型擦除
Java 1.5 才引入泛型,为了让新的泛型代码能在旧 JVM 上运行,同时兼容 1.5 之前的非泛型类库(如 List 原始类型),设计者选择了 "编译期检查 + 运行期擦除" 的折中方案。擦除后,泛型代码编译出的字节码,与旧版非泛型代码完全一致,无需修改 JVM 即可兼容。
What(是什么)
定义 :泛型擦除(Type Erasure)是 Java 编译器在编译阶段 执行的一种机制:将代码中所有泛型类型信息(<> 内的类型参数)删除,仅保留原始类型(Raw Type),使运行时看不到任何泛型痕迹。
本质:为实现向后兼容而设计的 "编译期伪装"。
Why(为什么)
核心原因:版本兼容
When(什么时候发生)
- 发生阶段 :编译期(javac 处理
.java文件生成.class字节码的过程中)。 - 不发生阶段:运行期(JVM 加载字节码后,泛型信息已不存在)。
Where(发生在哪里)
- 擦除的对象 :
- 变量声明、方法参数、返回值上的泛型标记(如
List<String>中的String、<? extends Fruit>中的边界)。
- 变量声明、方法参数、返回值上的泛型标记(如
- 不擦除的对象 :
- 堆中实例化的真实对象(如
new Apple()的类型信息永远保留)。 - 字节码中以
Signature属性存储的泛型签名(仅用于反射读取,不参与运行时逻辑)。
- 堆中实例化的真实对象(如
Who(由谁执行)
执行者:Java 编译器(javac)
- 开发者无需手动操作,擦除是编译器自动完成的过程。
- 开发者通过反射 API(如
getGenericType())可以读取字节码中残留的签名信息,但无法阻止擦除。
How(如何执行)
编译器遵循两条核心规则进行擦除:
- 无界泛型 :
<T>→ 擦除为Object- 示例:
public T get()→ 擦除后为public Object get()
- 示例:
- 有界泛型 :
<T extends Number>→ 擦除为上界类型(如Number)- 示例:
public void add(T t)→ 擦除后为public void add(Number t)
- 示例:
How Much / How(影响与结果)
直接结果
-
所有
List<String>、List<? extends Fruit>等类型,运行时均变为ArrayList(原始类型)。 -
运行时 JVM 无法区分不同泛型参数的容器类型。
带来的限制(由擦除直接导致)-下篇博客我们展开说
无法直接 new T() / new T[](运行时不知道 T 的具体类型)。
无法使用 instanceof 判断泛型类型(运行时无泛型信息)。
通配符 ? extends T 容器只能读、不能写(编译器无法确定安全的写入类型)
三、桥方法
What(是什么)
桥方法是 Java 编译器在编译阶段自动生成的隐形适配方法。作用:弥补泛型擦除带来的字节码方法签名差异,保证继承与多态机制正常运行,对开发者代码层完全隐藏。
Why(为什么出现)------泛型擦除导致
-
源码层规则:Java 源码方法签名 = 方法名 + 参数列表,不包含返回值,协变返回视为合法重写。
-
字节码 / JVM 规则:JVM 方法描述符 = 方法名 + 参数列表 + 返回值,返回值参与唯一区分。
-
泛型擦除后果(核心示例):
java// 1. 泛型父类 abstract class Parent<T> { public abstract T getValue(); // 泛型方法 } // 2. 子类指定具体类型,重写方法 class Child extends Parent<String> { @Override public String getValue() { // 具体返回值:String return "hello"; } }
-
泛型擦除后:
-
父类方法擦除 →
public abstract Object getValue();(返回值抹平为 Object) -
子类方法保留 →
public String getValue();(返回值仍为 String)此时,Object getValue()与String getValue()在字节码中是两个完全不同的方法,无法构成重写,多态直接断裂。
-
-
因此需要桥方法,补齐父类擦除后的方法签名,完成适配转发。
When(何时生成)
编译期由 javac 自动生成;触发条件:子类继承泛型父类 / 泛型接口,且重写了带有协变返回的泛型方法(如上述示例中 Child 重写 Parent 的 getValue 方法)。
Where(在哪里存在)
仅存在于子类 .class 字节码文件中;源码、IDE 大纲、常规调用均不可见,仅反射可检测。
Who(由谁生成、执行)
生成:Java 编译器(javac 自动处理)
调用:JVM 在多态向上转型时自动调用
编写:开发者无需手动定义、无感知
How(如何工作)
-
父类泛型方法经擦除,变为 Object 等上层类型返回值(如
Object getValue()); -
编译器在子类自动生成同参数、父类统一返回值的桥方法(隐形生成,源码不可见):
java// 编译器自动生成的桥方法(源码中看不到) public Object getValue() { // 内部调用开发者手写的真实实现方法,并完成类型强转 return this.getValue(); } -
桥方法内部强转并调用开发者手写的真实实现方法;
-
向上转型多态调用时,JVM 匹配并执行桥方法,间接执行子类逻辑:
java
// 多态调用示例
Parent p = new Child();
p.getValue(); // JVM 先调用桥方法,再转发到 String getValue()
How Much(带来的影响)
正面影响
-
修复泛型擦除造成的多态断裂问题;
-
兼容源码层协变返回的重写语法;
-
保证泛型继承体系在 JVM 层面合法运行。
负面影响 & 开发者感知
-
字节码冗余:类内部实际方法数量增加;
-
常规开发无感知:IDE 自动隐藏桥方法,日常编码完全感受不到;
-
反射场景会出现同名多方法(可通过反射代码检测)
四、总结
泛型------》安全优化------》约束类型------》老版本不兼容------》泛型擦除------》多态断裂------》打补丁------》桥方法
像不像我们写代码优化的过程:兼容,打补丁。。。。多么熟悉的感觉!
这篇文章,我们主要系统化的去认识泛型,泛型擦除,桥方法,以及他们之间的关系。因为篇幅有限,泛型的应用,泛型的其他影响,泛型与其他概念的关联,我们放到下一篇博客中阐述。bye~