PECS原则在Jackson中按读写角色选择通配符:序列化时用? extends T作为生产者安全读取,反序列化时用? super T作为消费者安全写入,组合使用实现来源宽松与目标兼容,避免泛型擦除导致的类型不匹配。

PECS 原则在 Jackson 序列化与反序列化组件的入参设计中,核心是明确每个泛型参数的“读写角色”,从而决定该用 ? extends T 还是 ? super T。它不是为了炫技,而是让 Jackson 工具方法既能安全接收业务中形形色色的集合类型,又不牺牲编译期类型检查。
序列化(输出)场景:用 ? extends T 保证下游可安全读取
当你把 Java 对象转成 JSON 字符串时,Jackson 的序列化器本身不往集合里写数据,而是从集合中“读出”元素进行转换——它是个生产者。
- 典型入参如:
<T> String writeValueAsString(Iterable<? extends T> value) - 调用方可传
List<UserV1>、Set<Admin>或Arrays.asList(new Guest()),只要UserV1、Admin、Guest都是T的子类(或就是T) - Jackson 内部能安全调用
iterator().next()并当作T处理,比如调用toString()或访问公共字段 - 禁止在该
Iterable上调用add()—— 因为实际可能是只读视图或不可变集合,编译器提前拦截风险
反序列化(输入)场景:用 ? super T 支持灵活填充结果容器
当你把 JSON 解析为 Java 对象并存入已有集合时,这个集合是“接收方”,要能容纳解析出的任意 T 实例及其子类——它是个消费者。
- 常见签名如:
<T> void readValues(JsonParser p, Collection<? super T> into) - 允许传入
ArrayList<Object>、LinkedList<Serializable>、甚至CopyOnWriteArrayList<ApiResponse>(只要T是其子类型) - Jackson 可以安全执行
into.add(parsedUser),因为parsedUser是T类型,而目标容器声明支持T及更宽泛的上层类型 - 但你不能从
into中直接按T类型取值 —— 编译器只允许返回Object,需手动转型或配合instanceof判断
工具方法组合设计:兼顾来源宽松与目标兼容
像 ObjectMapper.convertValue() 或自定义批量转换器这类桥接方法,往往同时涉及读源、写目标,此时 PECS 成对出现:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 签名示例:
<S, T> Collection<T> transform(Iterable<? extends S> src, Function<S, T> mapper, Collection<? super T> target) -
src用? extends S:上游可传List<OrderDTO>、Stream<LegacyOrder> -
target用? super T:下游可填入ArrayList<OrderEntity>、Vector<Serializable> - 整个链路无需用户做中间
new ArrayList<>()包装,也避免运行时ClassCastException
避坑提醒:别让泛型擦除破坏类型契约
Jackson 依赖反射和泛型元数据(如 Method.getGenericParameterTypes())推断类型。若你在入参中错误使用具体泛型(如硬写 List<User>),就会导致:
- 业务升级 DTO 后无法复用旧方法(
List<UserV2>不是List<User>的子类型) - 低代码平台中脚本节点无法对接不同版本模型,被迫加转换层
- 日志收集器、事件总线等通用组件失去“一次编写,多处接入”能力
用 PECS 不是妥协,而是把类型安全边界划清楚:读就只读,写就只写,职责分明,接口才真正可复用。

















