泛型擦除
# Java 泛型擦除详解
# 📚 什么是泛型擦除
泛型擦除(Type Erasure) 是Java泛型实现的核心机制。Java在编译时会将泛型信息擦除,在运行时JVM并不知道泛型的具体类型。这种设计是为了保持与Java 5之前版本的向后兼容性。
# 🔍 擦除的基本规则
- 无界类型参数 → 擦除为
Object - 有界类型参数 → 擦除为第一个边界类型
- 泛型方法 → 擦除类型参数
- 泛型类 → 擦除所有类型参数
// 编译前
List<String> stringList = new ArrayList<String>();
List<Integer> intList = new ArrayList<Integer>();
// 编译后(擦除)
List stringList = new ArrayList();
List intList = new ArrayList();
1
2
3
4
5
6
7
2
3
4
5
6
7
# 🛠️ 桥接方法(Bridge Methods)
考虑以下情况,假设你有一个泛型类和一个非泛型类之间的继承关系:
class Parent<T> {
public void onBindData(int position, T data) { }
}
class Child extends Parent<FragmentDataModel> {
@Override
public void onBindData(int position, FragmentDataModel data) {
// Implementation
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
在这种情况下,编译器可能会生成一个桥接方法来处理类型擦除问题:
public void onBindData(int position, Object data) {
onBindData(position, (FragmentDataModel) data);
}
1
2
3
2
3
这个桥接方法将确保在运行时正确地调用子类的 onBindData 方法,即使泛型类型已经被擦除。
# 🔍 桥接方法的工作原理
- 编译器检测:发现子类重写了泛型父类的方法
- 生成桥接:自动生成参数为Object类型的桥接方法
- 类型转换:在桥接方法中进行强制类型转换
- 方法调用:委托给实际的重写方法
# 📝 更多泛型擦除示例
# 1. 有界类型参数擦除
// 编译前
class NumberContainer<T extends Number> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
// 编译后(擦除为Number)
class NumberContainer {
private Number value;
public Number getValue() {
return value;
}
public void setValue(Number value) {
this.value = value;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 2. 泛型方法擦除
// 编译前
public class GenericMethod {
public <T> T process(T input) {
return input;
}
public <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
}
// 编译后
public class GenericMethod {
public Object process(Object input) {
return input;
}
public Comparable max(Comparable a, Comparable b) {
return a.compareTo(b) > 0 ? a : b;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3. 复杂继承场景
interface Processor<T> {
void process(T item);
}
abstract class BaseProcessor<T> implements Processor<T> {
@Override
public abstract void process(T item);
}
class StringProcessor extends BaseProcessor<String> {
@Override
public void process(String item) {
System.out.println("Processing: " + item);
}
// 编译器会生成桥接方法:
// public void process(Object item) {
// process((String) item);
// }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ⚠️ 泛型擦除的影响和限制
# 1. 运行时类型检查失效
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// 编译时错误,但说明了问题
// if (stringList.getClass() == intList.getClass()) {
// // 这个条件总是true,因为都是ArrayList.class
// }
System.out.println(stringList.getClass()); // class java.util.ArrayList
System.out.println(intList.getClass()); // class java.util.ArrayList
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 2. 不能创建泛型数组
// 编译错误
// List<String>[] arrays = new List<String>[10];
// 正确的方式
@SuppressWarnings("unchecked")
List<String>[] arrays = new List[10];
1
2
3
4
5
6
2
3
4
5
6
# 3. instanceof 检查限制
List<String> list = new ArrayList<>();
// 编译错误
// if (list instanceof List<String>) { }
// 只能检查原始类型
if (list instanceof List) {
// 可以执行
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 4. 静态上下文限制
class GenericClass<T> {
// 编译错误:不能在静态上下文中使用类型参数
// private static T staticField;
// 编译错误:不能在静态方法中使用类型参数
// public static void staticMethod(T param) { }
// 正确:静态泛型方法
public static <U> void staticGenericMethod(U param) {
// 可以执行
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 🎯 实际应用场景
# 1. 反射获取泛型信息
虽然运行时泛型被擦除,但在某些情况下仍可以获取泛型信息:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
abstract class GenericDAO<T> {
private Class<T> entityClass;
@SuppressWarnings("unchecked")
public GenericDAO() {
Type genericSuperclass = getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
entityClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
public Class<T> getEntityClass() {
return entityClass;
}
}
class UserDAO extends GenericDAO<User> {
// 可以通过反射获取到User.class
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2. 类型安全的工厂方法
public class TypeSafeFactory {
@SuppressWarnings("unchecked")
public static <T> T create(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException("Cannot create instance of " + clazz, e);
}
}
// 使用示例
public static void main(String[] args) {
String str = create(String.class); // 类型安全
Integer num = create(Integer.class); // 类型安全
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 💡 最佳实践和建议
# 1. 使用通配符处理协变和逆变
// 生产者使用 extends(协变)
public void processItems(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num);
}
}
// 消费者使用 super(逆变)
public void addNumbers(List<? super Integer> numbers) {
numbers.add(42);
numbers.add(100);
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 2. 避免原始类型
// 不推荐:原始类型
List rawList = new ArrayList();
rawList.add("String");
rawList.add(123); // 运行时可能出错
// 推荐:使用泛型
List<Object> objectList = new ArrayList<>();
objectList.add("String");
objectList.add(123); // 类型安全
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 3. 合理使用 @SuppressWarnings
@SuppressWarnings("unchecked")
public <T> T[] toArray(List<T> list, Class<T> componentType) {
// 为什么不使用new?
// 直接 new 数组(在泛型上下文中会有问题)
// @SuppressWarnings("unchecked")
// T[] array = (T[]) new Object[size];
T[] array = (T[]) Array.newInstance(componentType, list.size());
return list.toArray(array);
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 4. 泛型边界的合理设计
// 良好的边界设计
public class ComparableContainer<T extends Comparable<T>> {
private T value;
public void setValue(T value) {
this.value = value;
}
public boolean isGreaterThan(T other) {
return value.compareTo(other) > 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 🔧 调试和诊断技巧
# 1. 查看字节码
使用 javap 命令查看编译后的字节码:
javac MyGenericClass.java
javap -c -v MyGenericClass
1
2
2
# 2. 使用反射调试
public class GenericDebugger {
public static void printGenericInfo(Object obj) {
Class<?> clazz = obj.getClass();
System.out.println("Class: " + clazz.getName());
Type[] genericInterfaces = clazz.getGenericInterfaces();
for (Type type : genericInterfaces) {
System.out.println("Generic Interface: " + type);
}
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println("Generic Superclass: " + genericSuperclass);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 📊 总结
泛型擦除是Java泛型系统的核心特性,虽然带来了一些限制,但也保证了向后兼容性。理解泛型擦除对于:
- 编写类型安全的代码
- 理解编译器警告
- 正确使用反射API
- 避免常见的泛型陷阱
都至关重要。
桥接方法和合成方法是编译器为了确保类型擦除后的泛型代码和方法重写在运行时能够正确工作而生成的重要机制。
上次更新: 2025/10/09, 23:53:03