本文目录导读:

我来为你详细讲解泛型擦除(Type Erasure)的典型案例,包括原理、示例和注意事项。
什么是泛型擦除
Java 的泛型是编译期实现的,编译器会擦除所有的泛型类型信息,这意味着:
- 编译时:有完整的泛型类型检查
- 运行时:泛型信息完全消失
典型擦除案例
案例1:基本类型擦除
import java.util.ArrayList;
import java.util.List;
public class BasicErasure {
public static void main(String[] args) throws Exception {
// 编译时:泛型类型检查
List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// 运行时:两个 List 是同一个类型
System.out.println("strList 类型: " + strList.getClass());
System.out.println("intList 类型: " + intList.getClass());
System.out.println("是否相同类型: " +
(strList.getClass() == intList.getClass())); // true!
// 通过反射添加错误类型的数据
List<String> list = new ArrayList<>();
list.add("Hello");
// 利用反射绕过泛型检查
list.getClass().getMethod("add", Object.class)
.invoke(list, 123); // 可以添加 Integer!
System.out.println("List 内容: " + list);
// 输出: List 内容: [Hello, 123]
}
}
案例2:方法签名擦除
public class MethodErasure {
// 编译错误:方法签名冲突
// public void process(List<String> list) { }
// public void process(List<Integer> list) { }
// 无法共存!因为擦除后都是 List
// 解决方式:使用不同的方法名
public void processString(List<String> list) {
System.out.println("处理字符串列表");
for (String s : list) {
System.out.println(s.toUpperCase());
}
}
public void processInteger(List<Integer> list) {
System.out.println("处理整数列表");
for (Integer i : list) {
System.out.println(i * 2);
}
}
public static void main(String[] args) {
MethodErasure demo = new MethodErasure();
List<String> strings = List.of("java", "python");
List<Integer> integers = List.of(1, 2, 3);
demo.processString(strings);
demo.processInteger(integers);
}
}
案例3:继承中的泛型擦除
import java.util.ArrayList;
import java.util.List;
class Parent<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
class Child extends Parent<String> {
// 编译器会自动生成桥接方法
@Override
public String get() {
System.out.println("调用了 Child.get()");
return super.get();
}
// 桥接方法(编译器自动生成):
// public Object get() { return this.get(); }
}
public class InheritanceErasure {
public static void main(String[] args) throws Exception {
Child child = new Child();
child.set("Hello");
// 查看桥接方法
for (var method : Child.class.getDeclaredMethods()) {
System.out.println("方法: " + method.getName()
+ ", 参数: " + java.util.Arrays.toString(method.getParameterTypes())
+ ", 返回: " + method.getReturnType());
}
// 通过父类类型调用
Parent<String> parent = child;
System.out.println("获取值: " + parent.get());
}
}
案例4:泛型数组问题
import java.util.List;
public class ArrayErasure {
public static void main(String[] args) {
// 编译错误:不能直接创建泛型数组
// List<String>[] array = new List<String>[10]; // 错误!
// 正确方式:使用 ArrayList 代替
List<String>[] array = new List[10]; // 使用原始类型创建
// 或者使用 List<List<String>>
List<List<String>> nestedList = new java.util.ArrayList<>();
// 示例:泛型数组的实际问题
List<Integer> intList = List.of(1, 2, 3);
List<String> strList = List.of("a", "b", "c");
Object[] objects = new List[2];
objects[0] = intList;
objects[1] = strList;
// 运行时可能会抛出 ClassCastException
try {
List<Integer>[] intArrays = (List<Integer>[]) new List[2];
intArrays[0] = List.of(1, 2, 3);
// intArrays[1] = List.of("string"); // 运行时错误!
System.out.println("数组第一个元素: " + intArrays[0]);
} catch (Exception e) {
System.out.println("捕获异常: " + e);
}
}
}
案例5:工具类中的类型转换
import java.util.ArrayList;
import java.util.List;
public class TypeConversionErasure {
// 辅助方法:安全的类型转换
@SuppressWarnings("unchecked")
public static <T> List<T> castList(Object obj) {
if (obj instanceof List) {
return (List<T>) obj; // 运行时只是转成 List
}
return null;
}
// 显示运行时类型信息
public static void printRuntimeInfo() {
List<String> list = new ArrayList<>();
list.add("Hello");
System.out.println("=== 运行时泛型信息 ===");
System.out.println("List 运行时类型: " + list.getClass());
System.out.println("List 的父类: " + list.getClass().getGenericSuperclass());
// 查看类型参数(运行时已经擦除)
System.out.println("类型参数个数: " +
list.getClass().getTypeParameters().length);
}
public static void main(String[] args) {
printRuntimeInfo();
Object obj = new ArrayList<String>();
obj = List.of("a", "b", "c");
List<String> result = castList(obj);
if (result != null) {
System.out.println("转换成功: " + result);
}
}
}
案例6:泛型与重载
import java.util.List;
public class OverloadErasure {
// 以下两个方法不能共存!
// public void print(List<String> list) { }
// public void print(List<Integer> list) { }
// 正确方式:使用不同的方法名
public void printStringList(List<String> list) {
System.out.println("字符串列表: " + list);
}
public void printIntegerList(List<Integer> list) {
System.out.println("整数列表: " + list);
}
// 或者使用不同的参数个数
public void print(List<String> list, String... extra) {
System.out.println("带额外参数的字符串列表: " + list);
}
public static void main(String[] args) {
OverloadErasure demo = new OverloadErasure();
demo.printStringList(List.of("a", "b"));
demo.printIntegerList(List.of(1, 2));
demo.print(List.of("x"), "extra");
}
}
额外发现和解决方案
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
public class ErasureSolutions {
// 1. 通过 TypeToken 获取泛型类型
public static class TypeToken<T> {
private final Type type;
protected TypeToken() {
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof ParameterizedType) {
type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
} else {
throw new RuntimeException("无法获取泛型类型");
}
}
public Type getType() {
return type;
}
}
// 2. 类型安全的容器类
public static class SafeMap {
private final java.util.Map<String, Object> map = new java.util.HashMap<>();
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) map.get(key);
}
public <T> void put(String key, T value) {
map.put(key, value);
}
}
public static void main(String[] args) {
// 使用 TypeToken 获取泛型类型
TypeToken<List<String>> token = new TypeToken<List<String>>() {};
System.out.println("泛型类型: " + token.getType());
// 使用类型安全容器
SafeMap safeMap = new SafeMap();
safeMap.put("name", "张三");
safeMap.put("age", 25);
String name = safeMap.get("name");
Integer age = safeMap.get("age");
System.out.println("姓名: " + name + ", 年龄: " + age);
}
}
泛型擦除的核心要点:
- 编译期检查,运行期擦除:类型安全在编译时保证
- 不能创建泛型数组:运行时类型信息不足
- 不能重载擦除后相同的方法:方法签名冲突
- 桥接方法:JVM 自动生成以维持多态
- 运行时需手动转型:泛型信息丢失后需要显式转换
最佳实践:
- 使用
List而不是数组来保存泛型元素 - 使用明确的类型边界(
<? extends T>等) - 避免混合原始类型和泛型类型
- 使用类型令牌(TypeToken)获取运行时类型信息