JSON 反序列化中 TypeReference 的使用
最近在重构一个项目的后端的过程中,遇到了一个有关 JSON 反序列化的问题。
有这样的一段Java代码:
public List<String> getIPs() {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(this.broadcastServerIPs,
new TypeReference<List<String>>() {});
} catch (Exception e) {
return new ArrayList<>();
}
}这里使用的是 TypeReference 作为反序列化的模版。代码能正常编译,没有任何问题。
事实上,在最开始的第一版代码,AI给我推荐的是 List.class 作为反序列化的模版,但是代码检查出现了类型未知的提示,所以重新又让AI进行修改,得到了 TypeReference 的结果,此时不再出现报错。
为什么?
询问AI得知:Java 泛型只在编译期存在,在 Java 中,泛型类型在运行时会被擦除。也就是说,运行时 List<String> 和 List<Integer> 都是 List。List.class 无法携带 <String> 信息。这意味着当我们使用泛型时,运行时无法直接获取到泛型的具体类型信息。
因此,Jackson 在运行时“看不见”你想要的是 String,只能靠显式告诉它。
而TypeReference 的巧妙之处在于:通过匿名内部类获得泛型信息。虽然 Java 会擦除泛型,但这个匿名类的父类签名仍保留了 List<String> 的完整类型。Jackson 正是通过反射读取这个签名,准确还原出目标类型。
顺带一提,Spring Boot项目中的 @RequestBody 注解在接收请求体中的 List<User> 时,由于 Spring MVC 的存在,也会面临同样问题;
一种比较可行的解决方案是包装一层 DTO 对象:
// 使用 DTO 包装泛型集合
public class IPListDTO {
private List<String> ips;
// getter/setter ...
}
// 然后在 Spring Controller 中直接接收这个 DTO
@PostMapping("/updateIPs")
public void update(@RequestBody IPListDTO ipList) {
// ...
}这样避免了 顶层泛型 (直接暴露的泛型),绕开类型擦除问题。
Java 的泛型擦除是一把双刃剑:它保证了与旧版本的兼容性,却牺牲了运行时的类型信息。TypeReference 是 Jackson 社区在语言限制下的一种临时应对。只有理解底层机制,才能更好地写出优美的代码。
