guava的typetoken通过匿名子类利用class.getgenericsuperclass()获取保留的泛型签名,再经parameterizedtype.getactualtypearguments()等反射api提取类型参数,不违反类型擦除规则。

Guava 的 TypeToken 并不是凭空“恢复”泛型类型,而是利用 Java 反射机制中**类定义阶段保留的泛型签名信息**,通过匿名子类这一巧妙手段,在运行时重新捕获被擦除的类型参数。它不违背类型擦除规则,而是在规则允许的缝隙里精准取数。
匿名子类是关键突破口
Java 虽然在实例化对象时擦除泛型(如 new ArrayList<string>()</string> 运行时只有 ArrayList.class),但**类或接口的泛型声明**(如 class Box<t></t>)及其**继承/实现关系中的泛型实参**(如 new TypeToken<box>>() {}</box>),会作为字节码元数据保留在 Class.getGenericSuperclass() 中。
这个匿名子类本身没有逻辑,只起一个“占位+携带类型”的作用:
- 编译器为它生成一个带泛型签名的父类引用,例如:
TypeToken<box>></box> - 运行时调用
getClass().getGenericSuperclass(),返回的是ParameterizedType类型,内容就是TypeToken<box>></box> -
TypeToken内部解析该ParameterizedType,提取出实际类型参数Box<string></string>,再递归解析其内部泛型(如String)
底层依赖的核心反射 API
TypeToken 的实现高度依赖三个原生反射方法,全部来自 java.lang.Class 和 java.lang.reflect 包:
-
Class.getGenericSuperclass():获取当前类(匿名子类)的泛型父类,这是整个链条的起点 -
ParameterizedType.getActualTypeArguments():从泛型父类中提取实参列表,如[Box<string>]</string> -
TypeVariable.getBounds()和GenericArrayType.getGenericComponentType():用于处理T extends Number、T[]等复杂边界与数组场景
它不做任何字节码增强或运行时类生成,所有操作都在标准反射能力范围内完成。
为什么不能直接 new TypeToken>()?
语法上可以写,但必须是**匿名子类形式** —— 即末尾带 {}。这是因为:
- 只有匿名子类才能让 JVM 在其
getGenericSuperclass()中返回带具体类型参数的ParameterizedType - 如果写成
new TypeToken<list>>()</list>(无大括号),那只是调用TypeToken的构造方法,此时this.getClass()是TypeToken本身,它的泛型父类是原始的TypeToken,不含List<string></string> - 所以必须靠匿名子类“骗过”JVM,让它把泛型信息挂载到子类的继承链上
对通配符和类型变量的支持原理
TypeToken 能处理 ? extends Number 或 T,靠的是完整解析 WildcardType 和 TypeVariable 接口:
- 当泛型实参是
? extends Number & Runnable时,getUpperBounds()返回[Number, Runnable]数组 - 当遇到
T(类型变量),它会尝试沿继承链向上查找其声明位置(如方法或类定义),再读取其bounds -
TypeResolver类进一步支持基于上下文做类型变量替换,比如将Box<t></t>中的T替换为String
这些能力都建立在 Java 反射对 java.lang.reflect.Type 子类型的标准支持之上,TypeToken 只是做了更友好的封装和组合。











