Class<T>与reflect.TypeToken的区别及TypeToken适用场景问询
TypeToken 替代 Class<T>? 先看你给出的第一段代码,它用 Class<T> 指定集合元素类型,这种方式在元素类型是具体的非泛型类时工作得很好——比如反序列化 List<String>、List<User>(User是普通自定义类),直接传 String.class、User.class 完全没问题。但一旦遇到泛型嵌套、元素本身是泛型类型的场景,Class<T> 就捉襟见肘了,这时候必须用 TypeToken 来救场。
下面具体说几个必须用 TypeToken 的场景:
1. 元素本身是带泛型参数的类型
比如你要反序列化 List<Map<String, Integer>>,这里的元素是 Map<String, Integer>——它本身是泛型类型。如果用第一段代码,你只能传 Map.class,但 Map.class 只代表原始类型,丢失了 <String, Integer> 的泛型信息。Jackson 解析时会把 Map 里的键值都当成 Object 处理,没法自动转成 String 和 Integer。
这时候就需要用 TypeToken 捕获完整的元素类型:
// 用TypeToken获取Map<String, Integer>的完整类型 Type mapType = new TypeToken<Map<String, Integer>>() {}.getType(); JavaType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, mapType); List<Map<String, Integer>> result = objectMapper.readValue(loadFile("data.json"), listType);
2. 嵌套泛型的复杂结构
再比如 List<List<Long>>、Map<String, List<User>> 这种嵌套泛型的情况,Class<T> 根本没法表达这种多层泛型的结构。List.class 只能告诉你这是个 List,但不知道里面的元素又是个带泛型的 List;Map.class 也没法表示值是 List<User>。
这时候必须用 TypeToken 直接定义完整的目标类型:
// 直接定义List<List<Long>>的完整类型 TypeToken<List<List<Long>>> typeToken = new TypeToken<List<List<Long>>>() {}; List<List<Long>> result = objectMapper.readValue(loadFile("data.json"), typeToken.getType());
3. 自定义泛型类的反序列化
假设你有个自定义泛型类 Result<T>,现在要反序列化 List<Result<User>>。Result.class 只能代表原始的 Result 类型,丢失了 <User> 的泛型参数,Jackson 没法知道 Result 里的泛型字段该转成什么类型。
这时候用 TypeToken 就能精准捕获 Result<User> 的类型:
Type resultType = new TypeToken<Result<User>>() {}.getType(); JavaType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, resultType); List<Result<User>> result = objectMapper.readValue(loadFile("data.json"), listType);
4. 直接指定带泛型的返回类型(无需泛型参数传递)
如果你想写一个方法直接返回 List<Map<String, Integer>> 这种具体的泛型类型,而不是通过泛型参数 <T> 来传递,Class<T> 也办不到——因为没有一个 Class 能代表 Map<String, Integer>。这时候用 TypeToken 作为参数,就能让方法直接返回指定的泛型类型:
public static <T> T readJson(String filename, TypeToken<T> typeToken) throws IOException { return objectMapper.readValue(loadFile(filename), typeToken.getType()); } // 调用时直接指定完整类型 List<Map<String, Integer>> data = readJson("data.json", new TypeToken<List<Map<String, Integer>>>() {});
核心原因:Java 类型擦除
本质上是因为 Java 的泛型是编译时语法糖,运行时会擦除泛型参数信息。Class<T> 只能保存原始类型的信息,而 TypeToken 利用了匿名内部类的特性——在编译时,匿名内部类会保留它所使用的泛型参数的完整类型信息,运行时可以通过反射获取到这个类型,从而让 Jackson 能正确解析泛型结构。
内容的提问来源于stack exchange,提问作者johnwick0831

