泛型与通配符是旧系统向强类型架构迁移中的关键工程决策,需按“怎么配、在哪配、配到哪一层”精准落地:明确边界(如只读用? extends T、只写用? super T)、分层嵌套(适配层收敛、核心域禁用?)、规避擦除(用TypeToken补全运行时信息),并严格验证接口/实现、方法调用、第三方库三处泛型一致性。

旧系统向强类型架构迁移时,泛型与通配符不是“加不加”的选择题,而是“怎么配、在哪配、配到哪一层”的工程决策问题。盲目套用 <T> 或堆砌 ? extends 往往让编译通过但运行出错,关键在于把类型约束落在真正需要解耦或兼容的位置。
明确泛型边界:从“宽泛声明”转向“契约驱动”
老系统中常见 List 或 Map 无类型声明,迁移到强类型时不能直接改成 List<Object>——这等于没改。应依据实际使用场景反推上界或下界:
- 若只读取数据(如遍历展示),用
List<? extends Product>,允许传入List<Book>或List<Electronics> - 若只写入数据(如批量入库),用
List<? super Order>,支持接收List<Transaction>(假设Order是其子类) - 若需读写兼顾(如缓存操作),则必须使用具体类型参数,例如
Cache<String, User>,而非Cache<?, ?>
通配符分层嵌套:在API网关/适配层做收敛,不在核心域暴露
过渡期常需同时对接旧版 XML 接口(返回 Map<String, Object>)和新版 JSON 接口(返回 Response<User>)。此时应在适配器层封装通配符逻辑,而非让领域服务感知:
- 定义统一响应包装:
ApiResponse<T>,其中T在调用方确定,如ApiResponse<List<? extends BaseEntity>> - XML 解析器内部可使用
Map<String, ?>处理任意键值,但对外只暴露toDomainObject()方法,返回强类型实例 - 禁止在 DAO 层或实体类字段中出现
?—— 这会污染整个调用链,导致类型信息在编译期丢失
泛型擦除规避:用类型令牌(TypeToken)补全运行时泛型信息
Java 泛型擦除会导致 new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass(),这对依赖反射的旧框架(如某些 RPC 序列化器)是致命伤。过渡期可引入 TypeToken 显式传递类型:
- 将
jsonParser.fromJson(json, new TypeToken<List<User>>() {}.getType())替代fromJson(json, List.class) - 在 Spring 的
RestTemplate调用中,用ParameterizedTypeReference<List<User>>替代原始List.class - 自定义泛型工具类时,构造函数接收
Class<T>并缓存,用于后续instanceof判断或数组创建(如Array.newInstance(componentType, size))
迁移检查清单:三处必须验证的泛型一致性
每次合并泛型改造代码前,检查以下三点是否同步更新:
-
接口定义与实现类:若
Service<T>声明了泛型方法,其实现类必须显式继承ServiceImpl<User>,不能留空ServiceImpl<> -
泛型方法与调用上下文:避免
<T> T convert(Object src)这类无约束泛型方法;应改为<T extends DTO> T convert(Object src, Class<T> target) -
第三方库泛型兼容性:检查 Jackson、MyBatis、Lombok 等是否支持目标 JDK 版本下的泛型推导(如 Lombok 的
@Data对泛型字段生成 getter/setter 是否完整)

















