Java泛型因类型擦除导致运行时无法获取实际类型,Guava的TypeToken通过匿名子类机制将泛型信息固化到ParameterizedType中,从而绕过擦除限制,适用于Gson反序列化等场景。

Java 泛型在编译后会被擦除(type erasure),导致运行时无法直接获取泛型参数的实际类型(比如 List<string></string> 在运行时只剩 List)。Guava 的 TypeToken 通过利用 Java 的匿名子类机制,把泛型信息“固化”到运行时的 Type 对象中,从而绕过擦除限制。
利用匿名子类捕获泛型信息
TypeToken 的核心原理是:创建一个继承自 TypeToken<T> 的匿名子类,而 JVM 会在该子类的 Class 对象中保留其父类的泛型实际参数(即 ParameterizedType)。这是因为 Java 规范要求,当一个类以具体泛型形式继承泛型超类(如 new TypeToken<List<String>>() {})时,其字节码会记录完整的父类签名。
- 写法必须是匿名子类形式:
new TypeToken<List<String>>() {}(末尾的{}不可省略) - 不能直接写
new TypeToken<List<String>>()(会报错或返回Object类型) - 此时
getType()返回的是带泛型参数的ParameterizedType,可安全用于反序列化、类型检查等场景
常见使用场景示例
最典型的是配合 Gson 或 Jackson 做泛型反序列化:
- Gson 中:
gson.fromJson(json, new TypeToken<Map<String, User>>() {}.getType()) - Jackson 中:
mapper.readValue(json, new TypeReference<Map<String, User>>() {})(Jackson 自带类似机制,但 GuavaTypeToken可统一抽象) - 判断泛型类型:
TypeToken.of(new TypeToken<List<Integer>>() {}.getType()).isSubtypeOf(Collection.class)
注意边界情况和限制
TypeToken 并非万能,它依赖编译期已知的泛型字面量:
立即学习“Java免费学习笔记(深入)”;
- 不支持运行时拼接的泛型类型(例如
String clsName = "String"; TypeToken<?> t = new TypeToken<List<clsName>>() {}—— 这种语法根本不存在) - 无法还原通配符的具体上界/下界(
? extends Number会被简化为Number) - 对于类型变量(如方法泛型
<T> void f(T t)),若未在调用处固化,仍无法获取真实 T
替代方案与对比
除了 TypeToken,还有其他方式尝试保留泛型信息:
-
getClass().getGenericSuperclass()手动解析(TypeToken就是对其做了封装和健壮性增强) - 使用
Method.getGenericReturnType()或Field.getGenericType()(适用于反射获取声明处的泛型) - Lombok 的
@SneakyThrows+TypeToken组合简化写法(非必需,但可减少样板)


















