类型擦除使java泛型信息在运行时丢失,因泛型仅存在于编译期;类型令牌通过匿名子类保留泛型签名,再经反射获取parameterizedtype来恢复实际类型参数。

Java 泛型在编译后会经历类型擦除(Type Erasure),导致运行时无法直接获取泛型的实际类型参数(如 List<string></string> 中的 String)。但某些场景(如 JSON 反序列化、依赖注入、序列化框架)需要在运行时知道确切的泛型类型。这时,类型令牌(Type Token) 是一种常用且可靠的手法——它通过创建一个匿名子类来“捕获”泛型信息,并借助 Class#getGenericSuperclass() 提取真实类型。
为什么类型擦除让泛型信息丢失?
Java 泛型是编译期特性。例如:
List<string> list1 = new ArrayList(); List<integer> list2 = new ArrayList();</integer></string>
编译后二者都变成 List(原始类型),JVM 运行时无法区分它们的元素类型。因此,像 gson.fromJson(json, List.class) 会反序列化为 List<object></object>,丢失泛型语义。
类型令牌的核心原理:利用匿名子类绕过擦除
当定义一个匿名子类(如 new TypeToken<list>>() {}</list>)时,JVM 会在该子类的字节码中保留其父类的完整泛型签名(即 TypeToken<list>></list>)。我们可以通过反射读取这个签名,从而恢复被擦除的类型信息。
关键步骤包括:
- 定义一个泛型抽象类(如
TypeToken<t></t>),不提供具体实现,仅用于承载类型信息 - 创建其匿名子类实例:
new TypeToken<map user>>() {}</map> - 在构造器中调用
getClass().getGenericSuperclass(),获取带泛型的父类类型(ParameterizedType) - 从中提取实际类型参数(
getActualTypeArguments())
手写一个轻量 TypeToken 工具类
以下是一个简化但可用的实现(省略异常处理和边界检查):
public abstract class TypeToken<t> {
private final Type type;
protected TypeToken() {
// 获取当前匿名子类的直接父类(即 TypeToken<t> 的 ParameterizedType)
Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
@Override
public String toString() {
return type.toString();
}
}</t></t>
使用示例:
TypeToken<list>> token = new TypeToken<list>>() {};
System.out.println(token.getType()); // 输出:java.util.List<user></user></list></list>
注意:必须用 new TypeToken<...>() {}</...>(带大括号),否则 getClass() 返回的是 TypeToken 本身,没有泛型父类信息。
主流库中的应用与注意事项
Google Gson 和 Retrofit 均采用类似机制:
- Gson 使用
TypeToken<t></t>支持泛型反序列化:gson.fromJson(json, new TypeToken<list>>() {}.getType())</list> - Retrofit 的
Call<response>></response>也依赖TypeToken构造适配器
需注意的限制:
- 不能用于含类型变量的泛型(如方法内
<t> void foo(TypeToken<t> t)</t></t>),因为此时 T 是未绑定的类型变量,无法在运行时确定 - 不能用于基本类型数组(如
int[].class),需用包装类或显式指定int[].class - 在 Android ProGuard 或 R8 混淆下,若未保留泛型签名,可能失效(需配置
-keepattributes Signature)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











