ItemReader是生产者,用? extends T安全读取子类型数据;ItemWriter是消费者,用? super T安全写入父类型兼容数据,二者共同体现PECS原则。

PECS(Producer Extends, Consumer Super)是 Java 泛型中关于通配符使用的核心原则,它直接指导我们如何安全地读写泛型集合。Spring Batch 的 ItemReader 和 ItemWriter 接口设计,正是 PECS 原则的典型体现——一个只产出数据,一个只消费数据,职责清晰、类型安全。
ItemReader 是典型的 “Producer”,用 ? extends T
ItemReader<t></t> 接口定义为:
public interface ItemReader<T> {
T read() throws Exception;
}
它每次调用 read() 方法,**向外提供(produce)一个 T 类型的实例**,比如 User、Order 或 String。它不接收任何输入,只单向输出。
因此,当你把 ItemReader<? extends Person> 作为参数传入某方法时,该方法可以安全地调用 read() 并得到一个 Person 或其子类(如 Employee)的对象——这正是 Producer Extends 的适用场景。
立即学习“Java免费学习笔记(深入)”;
常见误用示例:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 写成
ItemReader<Object>去读User数据 → 类型太宽,编译通过但语义丢失,无法保证返回值是预期子类型; - 试图往
ItemReader里塞数据(比如设值)→ 接口根本不提供写入能力,违背其 producer 本质。
ItemWriter 是典型的 “Consumer”,用 ? super T
ItemWriter<t></t> 接口定义为:
public interface ItemWriter<T> {
void write(List<? extends T> items) throws Exception;
}
注意:方法参数是 List<? extends T>,但整个接口的泛型参数 T 表示它**能接受(consume)T 及其任意子类型的对象列表**。也就是说,ItemWriter<Person> 不仅能写 Person 实例,也能写 Employee、Customer 等子类实例——只要它们是 Person 的子类型。
这就对应 PECS 中的 Consumer Super:若你希望一个 writer 能处理更宽泛的输入(比如所有 Person 子类),应声明为 ItemWriter<? super Person>,这样你就能传入 ItemWriter<Object> 或 ItemWriter<Person> 等兼容类型。
实际开发中更常见的写法是直接使用具体类型:
-
@Bean public ItemWriter<User> userWriter() { ... }→ 明确语义,推荐; - 若需复用 writer 处理多种子类型,可考虑上界通配符,例如方法签名:
void configureWriter(ItemWriter<? super User> writer); - 避免写成
ItemWriter<? extends User>→ 这意味着“只能写未知的 User 子类”,但你根本不知道具体是什么类型,无法安全传入任何实例,编译会拒绝。
对比总结:读出 vs 写入,方向决定通配符
二者在 PECS 下的差异本质是数据流向不同:
-
ItemReader<T>是 生产者:向外吐T,所以适合? extends T(向上兼容,读得更具体); -
ItemWriter<T>是 消费者:向内收T,所以适合? super T(向下兼容,写得更通用); - Spring Batch 框架本身没有在接口中硬编码通配符,但它的方法签名(
read()返回T,write(List<? extends T>))已隐含 PECS 意图,开发者在组合、泛型抽象或封装工具类时必须遵循; - 违反 PECS 往往导致编译错误或运行时类型异常,比如把
ItemWriter<Employee>强转为ItemWriter<Person>会失败——因为Employeewriter 不能安全处理任意Person实例(可能含Customer字段)。
理解这点,能帮你写出更健壮的泛型批处理组件,也更容易看懂 Spring Batch 源码中各类 reader/writer 的泛型约束逻辑。

















