一、泛型(Generics)本质
1. 核心概念
泛型是JDK 5引入的参数化类型机制,允许在定义类、接口或方法时使用类型参数(Type Parameters),其主要目的是:
-
类型安全:编译时检查类型一致性
-
代码复用:编写可适用于多种类型的通用代码
-
消除强制类型转换:减少运行时ClassCastException
2. 基本语法
// 泛型类
class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
// 泛型方法
public static <E> void printArray(E[] array) {
for (E element : array) {
System.out.print(element + " ");
}
}
二、泛型擦除(Type Erasure)
1. 擦除机制原理
Java泛型是编译期特性,虚拟机层面不存在泛型类型,编译器会执行以下转换:
-
类型参数替换:
-
无界类型参数(如
<T>)→ Object -
有界类型参数(如
<T extends Number>)→ 边界类型(Number)
-
-
插入类型转换:在需要时自动插入checkcast指令
-
生成桥接方法:保持多态性
2. 擦除前后对比
// 源代码
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);
// 擦除后等效代码
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 编译器插入的类型转换
三、擦除带来的限制
1. 运行时类型查询失效
List<String> strList = new ArrayList<>();
System.out.println(strList instanceof List<String>); // 编译错误
System.out.println(strList instanceof List); // 正确
2. 不能创建泛型数组
// 编译错误
List<String>[] array = new List<String>[10];
3. 类型参数不能实例化
class Box<T> {
public void create() {
new T(); // 编译错误
}
}
四、绕过擦除的限制
1. 类型令牌(Class对象)
public class GenericFactory<T> {
private Class<T> type;
public GenericFactory(Class<T> type) {
this.type = type;
}
public T createInstance() throws Exception {
return type.getDeclaredConstructor().newInstance();
}
}
// 使用
GenericFactory<String> factory = new GenericFactory<>(String.class);
String s = factory.createInstance();
2. 反射获取实际类型
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
abstract class TypeReference<T> {
private final Type type;
protected TypeReference() {
Type superClass = getClass().getGenericSuperclass();
this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
}
// 使用
Type type = new TypeReference<List<String>>() {}.getType();
System.out.println(type); // 输出: java.util.List<java.lang.String>
五、泛型与继承
1. 泛型类继承
class NumericBox<T extends Number> extends Box<T> {
public double getAsDouble() {
return get().doubleValue();
}
}
2. 通配符类型
| 类型 | 说明 | 示例 |
|---|---|---|
<?> | 无界通配符(任何类型) | List<?> |
<? extends T> | 上界通配符(T或其子类) | List<? extends Number> |
<? super T> | 下界通配符(T或其父类) | List<? super Integer> |
3. PECS原则(Producer-Extends, Consumer-Super)
// 生产者使用extends
public static double sum(List<? extends Number> list) {
double sum = 0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}
// 消费者使用super
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
六、深度问题
Q1:为什么Java要采用类型擦除?
A:主要考虑向后兼容:
-
保证泛型代码能与旧版本JVM兼容
-
不需要修改JVM规范
-
平滑过渡到泛型系统
Q2:泛型擦除后如何保证类型安全?
A:通过两个机制:
-
编译时类型检查:确保代码中类型使用正确
-
自动插入类型转换:在字节码层面添加checkcast指令
Q3:List<String>和List<Integer>擦除后是同一个类吗?
A:是的!擦除后都变成List,但编译器会在使用时自动插入对应类型的转换:
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
// 擦除后都变为ArrayList,但:
// list1.add("str") → 编译为add(Object) + 运行时检查
// list2.add(1) → 自动装箱为Integer + add(Object)
Q4:如何实现类似new T()的功能?
A:三种方案:
-
传入Class对象(类型令牌)
static <T> T create(Class<T> clazz) { return clazz.newInstance(); } -
使用Supplier函数式接口
static <T> T create(Supplier<T> supplier) { return supplier.get(); } -
通过反射获取类型参数(复杂场景)
理解泛型和类型擦除机制是掌握Java高级特性的关键,这有助于编写更安全、更灵活的通用代码,同时也能正确处理泛型相关的各种边界情况。

1617

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



