PECS原则指导MapStruct泛型映射方法设计:输入集合用? extends确保安全读取(生产者),输出集合用? super确保安全写入(消费者),违反则编译失败。

PECS 原则在 MapStruct 中不直接参与映射逻辑,但它深刻影响你如何设计自定义映射方法的泛型签名——尤其当你在 @Mapper 接口中声明带泛型参数的映射方法时。
为什么 MapStruct 方法需关注 PECS
MapStruct 生成的实现类会调用你写的自定义方法。若这些方法接收或返回泛型集合(如 List<? extends Source> 或 Set<? super Target>),编译器是否允许调用、能否安全读写,完全取决于你是否遵守 PECS。MapStruct 本身不解析通配符含义,但 Java 编译器会严格校验。
作为生产者:用 ? extends 读取源对象
当你的自定义方法从输入集合中“读取”元素并转换为新对象时,输入集合是生产者:
- 声明为
List<? extends PersonSource>—— 可接受List<EmployeeSource>、List<CustomerSource>(二者都继承PersonSource) - 方法体内可安全调用
sourceList.get(i).getName(),因为所有元素至少是PersonSource - 但不能向该集合添加任何新元素(包括
new PersonSource()),类型系统禁止
作为消费者:用 ? super 写入目标集合
当你的方法需要把转换后的对象“写入”某个已有集合时,该集合是消费者:
立即学习“Java免费学习笔记(深入)”;
- 声明为
Collection<? super UserDto>—— 可传入ArrayList<Object>、HashSet<ApiResponse>(只要它们能容纳UserDto) - 方法内可安全调用
targetCollection.add(userDto),因为UserDto是其元素类型的子类型 - 但无法从该集合中安全读取具体类型(如
targetCollection.iterator().next()返回的是Object)
MapStruct 实际写法示例
假设你有 EmployeeSource 和 ContractorSource 都继承 PersonSource,要统一映射为 PersonDto:
@Mapper
public interface PersonMapper {
// 输入是生产者:只读,支持子类型
List<PersonDto> mapAll(List<? extends PersonSource> sources);
@AfterMapping
default void handleSpecialFields(PersonSource source, @MappingTarget PersonDto target) {
// 可以安全访问 source 的公共字段(如 name, id)
if (source instanceof EmployeeSource es) {
target.setRole("EMPLOYEE");
}
}
}
若误写成 List<PersonSource>,调用方传 List<EmployeeSource> 就会编译失败;用 ? extends 则兼容。
不复杂但容易忽略


















