问题重现:泛型擦除的困扰
在实际开发中,我们经常遇到这样的场景:收到一串用户列表的JSON数据,需要反序列化为List<UserDTO>:
json
[
{"id": 1, "name": "张三", "email": "zhangsan@example.com"},
{"id": 2, "name": "李四", "email": "lisi@example.com"}
]
如果直接这样写代码:
java
ObjectMapper objectMapper = new ObjectMapper();
List<UserDTO> userList = objectMapper.readValue(jsonStr, List.class);
System.out.println(userList.get(0).getName()); // 运行时报错!
运行时会抛出:java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to UserDTO
根本原因在于Java的泛型擦除机制。
泛型擦除机制解析
Java的泛型是编译期特性,运行时泛型信息会被擦除:
java
// 编译期
List<UserDTO> list = new ArrayList<>();
// 运行时(JVM看到的样子)
List list = new ArrayList(); // 泛型信息消失了
由于运行时无法获取List<UserDTO>的类型信息,JSON库只能将其解析为List<Map>。
主流JSON库的解决方案
Jackson:TypeReference方案
Jackson通过TypeReference保留泛型信息:
java
ObjectMapper objectMapper = new ObjectMapper();
List<UserDTO> userList = objectMapper.readValue(jsonStr,
new TypeReference<List<UserDTO>>() {});
原理分析: TypeReference利用匿名内部类的特性,在运行时通过反射获取父类的泛型参数:
java
public abstract class TypeReference<T> {
private final Type _type;
protected TypeReference() {
Type superClass = getClass().getGenericSuperclass();
_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
}
Gson:TypeToken方案
Gson提供了类似的TypeToken机制:
java
Gson gson = new Gson();
Type userListType = new TypeToken<List<UserDTO>>(){}.getType();
List<UserDTO> userList = gson.fromJson(jsonStr, userListType);
Fastjson2:TypeReference方案
Fastjson2作为阿里开源的高性能JSON库,同样支持泛型处理:
java
// Fastjson2的TypeReference使用
String jsonStr = "[{\"id\":1,\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}]";
List<UserDTO> userList = JSON.parseObject(jsonStr,
new TypeReference<List<UserDTO>>() {});
复杂场景处理
嵌套泛型
java
// JSON: {"code": 200, "data": [{"id": 1, "name": "张三"}]}
public class ApiResponse<T> {
private int code;
private T data;
}
// Jackson
ApiResponse<List<UserDTO>> response = objectMapper.readValue(
jsonStr, new TypeReference<ApiResponse<List<UserDTO>>>() {});
// Fastjson2
ApiResponse<List<UserDTO>> response = JSON.parseObject(
jsonStr, new TypeReference<ApiResponse<List<UserDTO>>>() {});
Map类型
java
// JSON: {"1": {"name": "张三"}, "2": {"name": "李四"}}
// Jackson
Map<String, UserDTO> userMap = objectMapper.readValue(
jsonStr, new TypeReference<Map<String, UserDTO>>() {});
// Fastjson2
Map<String, UserDTO> userMap = JSON.parseObject(
jsonStr, new TypeReference<Map<String, UserDTO>>() {});
在Spring框架中的应用
Feign客户端
java
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/api/users")
ResponseEntity<List<UserDTO>> getUsers();
}
WebClient
java
// Spring WebFlux的WebClient
List<UserDTO> users = webClient.get()
.uri("/api/users")
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<UserDTO>>() {})
.block();
工程实践建议
1. 预定义常用类型
java
public class TypeReferences {
public static final TypeReference<List<UserDTO>> USER_LIST =
new TypeReference<List<UserDTO>>() {};
public static final TypeReference<Map<String, UserDTO>> USER_MAP =
new TypeReference<Map<String, UserDTO>>() {};
public static final TypeReference<ApiResponse<List<UserDTO>>> API_USER_LIST =
new TypeReference<ApiResponse<List<UserDTO>>>() {};
}
2. 性能优化策略
在高并发场景下,考虑缓存TypeReference实例:
java
@Component
public class TypeReferenceCache {
private static final Map<String, TypeReference<?>> CACHE =
new ConcurrentHashMap<>();
@SuppressWarnings("unchecked")
public static <T> TypeReference<T> get(String key,
Supplier<TypeReference<T>> supplier) {
return (TypeReference<T>) CACHE.computeIfAbsent(key, k -> supplier.get());
}
}
常见误区
误区1:数组类型误用
java
// ❌ 错误:数组类型不需要TypeReference
UserDTO[] users = objectMapper.readValue(jsonStr, UserDTO[].class);
// ✅ 正确:数组类型直接传class即可
误区2:库混淆
java
// Jackson的TypeReference
import com.fasterxml.jackson.core.type.TypeReference;
// Spring的ParameterizedTypeReference
import org.springframework.core.ParameterizedTypeReference;
// Fastjson2的TypeReference
import com.alibaba.fastjson2.TypeReference;
误区3:异常处理不当
java
// ❌ 错误:不处理异常
try {
List<UserDTO> users = objectMapper.readValue(jsonStr,
new TypeReference<List<UserDTO>>() {});
} catch (IOException e) {
// 应该记录日志并采取相应措施
log.error("JSON反序列化失败", e);
throw new BusinessException("数据格式错误");
}
技术选型建议
| JSON库 | 性能 | 特性 | 适用场景 |
|---|---|---|---|
| Jackson | 中等 | 功能全面,生态成熟 | Spring全家桶,企业级应用 |
| Gson | 较低 | API简洁,Android友好 | 移动端,轻量级应用 |
| Fastjson2 | 高 | 性能优异,功能丰富 | 高并发,性能敏感场景 |
选择建议:
- Spring项目:优先Jackson,生态兼容性好
- 性能要求高:选择Fastjson2
- Android开发:考虑Gson
总结
泛型擦除是Java语言的特性,但通过各JSON库提供的TypeReference/TypeToken机制,我们可以优雅地解决这个问题。掌握不同JSON库的泛型处理方法,能够让我们在实际项目中根据需求做出最合适的技术选型。