1. 概述
本文将深入讲解如何使用 MapStruct 实现对象集合的映射。
前提是你已经对 MapStruct 有基本了解。如果还不熟悉,建议先阅读我们的 MapStruct 快速入门指南。
本文重点聚焦于集合映射的常见场景和高级策略,适合有一定使用经验的开发者参考,避免踩坑。
2. 集合映射基础
MapStruct 对集合的映射机制与基本类型一致:✅ 你只需定义接口方法,剩下的交给 MapStruct 自动生成。
其核心逻辑非常简单粗暴:
自动生成的代码会遍历源集合,逐个调用元素映射方法,最终组装成目标集合。
下面通过几个典型示例展开说明。
2.1 映射 List
先定义源对象 Employee
:
public class Employee {
private String firstName;
private String lastName;
// 构造函数、getter 和 setter 省略
}
目标 DTO 类:
public class EmployeeDTO {
private String firstName;
private String lastName;
// getter 和 setter
}
定义映射接口:
@Mapper
public interface EmployeeMapper {
List<EmployeeDTO> map(List<Employee> employees);
}
MapStruct 会自动生成如下实现类:
public class EmployeeMapperImpl implements EmployeeMapper {
@Override
public List<EmployeeDTO> map(List<Employee> employees) {
if (employees == null) {
return null;
}
List<EmployeeDTO> list = new ArrayList<EmployeeDTO>(employees.size());
for (Employee employee : employees) {
list.add(employeeToEmployeeDTO(employee));
}
return list;
}
protected EmployeeDTO employeeToEmployeeDTO(Employee employee) {
if (employee == null) {
return null;
}
EmployeeDTO employeeDTO = new EmployeeDTO();
employeeDTO.setFirstName(employee.getFirstName());
employeeDTO.setLastName(employee.getLastName());
return employeeDTO;
}
}
关键点:
- MapStruct 自动推导出了
Employee → EmployeeDTO
的单个对象映射逻辑 ✅ - 无需手动定义单个对象映射方法,只要字段名一致即可
⚠️ 但当字段不匹配时,自动映射会失败。例如目标对象为:
public class EmployeeFullNameDTO {
private String fullName;
// getter 和 setter
}
此时若只声明:
@Mapper
public interface EmployeeFullNameMapper {
List<EmployeeFullNameDTO> map(List<Employee> employees);
}
编译时会收到警告:
Warning:(11, 31) java: Unmapped target property: "fullName".
Mapping from Collection element "com.baeldung.mapstruct.mappingCollections.model.Employee employee" to
"com.baeldung.mapstruct.mappingCollections.dto.EmployeeFullNameDTO employeeFullNameDTO".
❌ 原因:MapStruct 无法自动拼接 firstName + lastName
生成 fullName
。
✅ 解决方案:手动提供单个对象映射方法:
@Mapper
public interface EmployeeFullNameMapper {
List<EmployeeFullNameDTO> map(List<Employee> employees);
default EmployeeFullNameDTO map(Employee employee) {
EmployeeFullNameDTO dto = new EmployeeFullNameDTO();
dto.setFullName(employee.getFirstName() + " " + employee.getLastName());
return dto;
}
}
此时 MapStruct 会自动调用这个 map(Employee)
方法来处理集合中的每个元素。
📌 总结:只要你在 Mapper 中提供了
S → T
的映射方法,MapStruct 就能自动用于List<S> → List<T>
等集合映射。
2.2 映射 Set 和 Map
Set 的映射方式与 List 完全一致:
@Mapper
public interface EmployeeMapper {
Set<EmployeeDTO> map(Set<Employee> employees);
}
生成代码会使用 HashSet
作为默认实现:
public class EmployeeMapperImpl implements EmployeeMapper {
@Override
public Set<EmployeeDTO> map(Set<Employee> employees) {
if (employees == null) return null;
Set<EmployeeDTO> set = new HashSet<>(Math.max((int)(employees.size() / .75f) + 1, 16));
for (Employee employee : employees) {
set.add(employeeToEmployeeDTO(employee));
}
return set;
}
// ...
}
Map 的映射也类似:
@Mapper
public interface EmployeeMapper {
Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap);
}
生成代码会遍历 entry 并逐个转换 value:
public class EmployeeMapperImpl implements EmployeeMapper {
@Override
public Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap) {
if (idEmployeeMap == null) return null;
Map<String, EmployeeDTO> map = new HashMap<>(Math.max((int)(idEmployeeMap.size() / .75f) + 1, 16));
for (java.util.Map.Entry<String, Employee> entry : idEmployeeMap.entrySet()) {
String key = entry.getKey();
EmployeeDTO value = employeeToEmployeeDTO(entry.getValue());
map.put(key, value);
}
return map;
}
// ...
}
✅ 所有集合类型(List/Set/Map)的映射逻辑高度统一,掌握一种即可类推。
3. 集合映射策略(Collection Mapping Strategy)
当目标对象包含集合字段时,MapStruct 提供多种策略来决定如何“填充”该集合。
通过 @Mapper(collectionMappingStrategy = ...)
配置,可选值包括:
ACCESSOR_ONLY
(默认):仅使用 setterSETTER_PREFERRED
:优先 setter,无则用 adderADDER_PREFERRED
:优先 adder,无则用 setter ✅ 推荐用于构建者模式TARGET_IMMUTABLE
:目标不可变,需构造时初始化
3.1 ACCESSOR_ONLY 策略
这是默认策略,优先调用 setter 方法。
示例源对象:
public class Company {
private List<Employee> employees;
// getter 和 setter
}
目标 DTO(同时提供 setter 和 adder):
public class CompanyDTO {
private List<EmployeeDTO> employees;
public List<EmployeeDTO> getEmployees() { return employees; }
public void setEmployees(List<EmployeeDTO> employees) { this.employees = employees; }
public void addEmployee(EmployeeDTO employeeDTO) {
if (employees == null) employees = new ArrayList<>();
employees.add(employeeDTO);
}
}
Mapper 定义:
@Mapper(uses = EmployeeMapper.class)
public interface CompanyMapper {
CompanyDTO map(Company company);
}
生成代码:
public class CompanyMapperImpl implements CompanyMapper {
private final EmployeeMapper employeeMapper = Mappers.getMapper(EmployeeMapper.class);
@Override
public CompanyDTO map(Company company) {
if (company == null) return null;
CompanyDTO dto = new CompanyDTO();
dto.setEmployees(employeeMapper.map(company.getEmployees())); // 使用 setter
return dto;
}
}
✅ 特点:简洁高效,但要求集合字段可被直接替换。
3.2 ADDER_PREFERRED 策略
当你希望逐个添加元素时(如集合为 final 或使用构建者模式),应使用此策略。
配置方式:
@Mapper(
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
uses = EmployeeMapper.class
)
public interface CompanyMapperAdderPreferred {
CompanyDTO map(Company company);
}
⚠️ 注意:此时必须显式提供单个对象映射方法,否则无法编译:
@Mapper
public interface EmployeeMapper {
EmployeeDTO map(Employee employee); // 必须存在!
List<EmployeeDTO> map(List<Employee> employees);
// 其他集合方法...
}
生成代码将使用 adder 逐个添加:
public class CompanyMapperAdderPreferredImpl implements CompanyMapperAdderPreferred {
private final EmployeeMapper employeeMapper = Mappers.getMapper(EmployeeMapper.class);
@Override
public CompanyDTO map(Company company) {
if (company == null) return null;
CompanyDTO dto = new CompanyDTO();
if (company.getEmployees() != null) {
for (Employee employee : company.getEmployees()) {
dto.addEmployee(employeeMapper.map(employee)); // 逐个 add
}
}
return dto;
}
}
📌 适用场景:
- 目标对象集合字段为 final
- 使用 Lombok
@Singular
生成 adder- 需要懒加载或按条件添加元素
4. 目标集合的实现类型
MapStruct 在生成代码时会为集合接口选择默认实现类,例如:
接口类型 | 默认实现 |
---|---|
List |
ArrayList |
Set |
HashSet |
Map |
HashMap |
你可以在 官方文档 查看完整映射表。
⚠️ 注意:这些实现是写死在生成代码中的,无法通过配置更改。若需特定实现(如
LinkedHashSet
),建议在目标对象中直接声明具体类型,或使用@ObjectFactory
自定义。
5. 总结
本文系统梳理了 MapStruct 集合映射的核心机制与最佳实践:
- ✅ 集合映射本质是“循环 + 单个对象映射”
- ✅ 字段名一致时自动映射,否则需手动提供转换逻辑
- ✅ List/Set/Map 映射方式统一,API 设计简洁
- ✅
ADDER_PREFERRED
策略更适合复杂对象构建场景 - ✅ 默认使用常见集合实现类(ArrayList/HashSet/HashMap)
掌握这些技巧后,你可以更高效地处理 DTO 转换,避免手写重复的 for 循环代码。
完整示例代码已上传至 GitHub:https://github.com/tech-tutorial/mapstruct-collections-demo