第一部分:泛型的本质------从"类型强制转换"说起
在 Java 5 之前,我们的 ArrayList 只能存储 Object。这意味着你每次取数据都要手动强转,代码里充斥着 (String) list.get(0)。
1. 为什么需要泛型?
-
编译期检查:将类型问题从"运行期报错"提前到"编译期报错"。
-
代码复用 :一套代码可以处理多种数据类型(如一个
Result<T>统一包装所有接口返回值)。 -
性能提升:减少了不必要的强制类型转换。
第二部分:泛型的核心用法------类、接口与方法
作为架构师,你必须能熟练编写可扩展的泛型组件。
1. 泛型类
在类名后使用 <T> 标识。
Java
public class GenericResult<T> {
private int code;
private T data; // 数据类型由外部指定
public GenericResult(T data) {
this.data = data;
}
}
2. 泛型方法
注意:泛型方法不一定要在泛型类中。
Java
public <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", element);
}
}
第三部分:底层黑幕------类型擦除 (Type Erasure)
这是面试官最喜欢深挖的地方:"Java 的泛型是真泛型还是伪泛型?"
1. 擦除机制
Java 的泛型是在编译器层面实现的。在生成的字节码(Bytecode)中,泛型信息会被擦除。
-
如果没有指定边界(如
<T>),擦除为Object。 -
如果指定了边界(如
<T extends Number>),擦除为Number。
2. 带来的影响
-
不能使用基本类型 :不能定义
List<int>,只能用List<Integer>。 -
运行时类型丢失 :你不能通过
if (list instanceof List<String>)来判断,因为运行时它只是List。 -
泛型数组禁令 :不能直接创建泛型数组
new T[10]。
第四部分:通配符的艺术------PECS 原则
面试官:"List<Number> 和 List<Integer> 是继承关系吗?"
回答 :不是。虽然 Integer 是 Number 的子类,但 List<Integer> 并不是 List<Number> 的子类。这叫不协变。
为了解决这个问题,我们需要通配符 ?。
1. 上界通配符 <? extends T>
-
含义:表示类型是 T 或 T 的子类。
-
特点 :只能读 ,不能写(除了 null)。
-
场景:作为生产者(Producer),往外提供数据。
2. 下界通配符 <? super T>
-
含义:表示类型是 T 或 T 的父类。
-
特点 :只能写 ,不能安全地读(只能读出 Object)。
-
场景:作为消费者(Consumer),往里存数据。
3. 💡 架构师口诀:PECS (Producer Extends, Consumer Super)
如果你要从集合中读取数据,使用 extends;如果你要向集合中写入数据,使用 super。
第五部分:实战案例------如何利用泛型重构冗余代码
假设你在做一个支付系统,有不同的支付响应。
Java
// 使用泛型方法实现通用的响应解析
public class PayProcessor {
public <T extends BaseResponse> T processPay(String rawData, Class<T> clazz) {
// 利用泛型和反射将 JSON 转化为具体对象
T response = JSON.parseObject(rawData, clazz);
if (response.isSuccess()) {
log.info("支付成功: {}", response.getTradeNo());
}
return response;
}
}
第六部分:面试复盘脑图
Code snippet
mindmap
root((Java 泛型与通配符))
核心价值
编译期检查
消除强制类型转换
提升代码复用
基本语法
泛型类/接口: <T>
泛型方法: <E>
底层原理
类型擦除: 伪泛型, 编译后变 Object/边界
泛型翻译: 自动插入 CheckCast
通配符与 PECS
<? extends T>: 上界, 生产者, 宜读不宜写
<? super T>: 下界, 消费者, 宜写不宜读
<?>: 无界通配符, 仅能读出 Object
限制与坑
不能实例化 T: new T() 是非法的
无法获取运行期泛型类: list.getClass() 结果一致
静态环境限制: 静态方法/变量无法引用类定义的 T
第七部分:大厂面试官的"夺命连环炮"
-
List<Object>和List<?>有什么区别?- 回答要点 :
List<Object>是确定的类型,可以往里面添加任何对象。List<?>是不确定的类型,它表示某种特定类型的 List,但在不知道具体是什么前,禁止往里面添加元素(除了 null)。
- 回答要点 :
-
泛型信息被擦除了,那反射是怎么拿到泛型的?
- 回答要点 :虽然方法体内部的泛型会被擦除,但类定义、字段定义、方法签名 上的泛型信息会保存在类文件的
Signature属性中。我们可以通过getGenericSuperclass()等反射 API 找回。
- 回答要点 :虽然方法体内部的泛型会被擦除,但类定义、字段定义、方法签名 上的泛型信息会保存在类文件的
-
既然有类型擦除,为什么重载(Overload)时不能只改变泛型参数?
- 回答要点 :因为在字节码层面,擦除后的方法签名(Method Signature)是完全一样的。比如
method(List<String>)和method(List<Integer>)擦除后都是method(List),JVM 无法区分。
- 回答要点 :因为在字节码层面,擦除后的方法签名(Method Signature)是完全一样的。比如
结语:从"会用"到"精通"
泛型是 Java 强类型语言中给予开发者的"灵活性补丁"。 不懂泛型,你永远只能写业务逻辑;精通泛型,你才能写出像 Spring、MyBatis 这样优秀的框架。
这篇文章总结了泛型中最难啃的骨头。如果你能在面试中流利地解释 PECS 并在白板上写出泛型擦除后的逻辑,那么 Offer 已经向你招手了。