1. 概述
本文将详细介绍如何使用 ModelMapper 框架完成不同类型元素列表之间的映射。核心思路是借助 Java 的泛型机制,实现数据在不同 List 类型之间的转换。
✅ 重点:解决泛型擦除导致的类型丢失问题
⚠️ 踩坑提示:直接传 List<T>.class
是无效的,必须使用 TypeToken
2. ModelMapper 基础
ModelMapper 的核心功能是对象之间的属性映射,常用于实体类(Entity)与数据传输对象(DTO)之间的自动转换,减少手动 set/get 的样板代码。
要使用 ModelMapper,首先在 pom.xml
中引入依赖:
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.0</version>
</dependency>
2.1. 配置建议
ModelMapper 提供了丰富的配置选项来控制映射行为。一个常见的最佳实践是开启字段匹配,尤其是对私有字段的支持:
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true) // 允许匹配私有字段
.setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE);
这样即使目标类的字段是 private
,也能完成映射。
关于匹配策略(Matching Strategy),默认的 STANDARD
策略要求源和目标的所有属性都能按名称匹配,顺序不限。这种策略在大多数场景下表现良好,推荐保持默认。
2.2. TypeToken:解决泛型擦除的关键
Java 的泛型在运行时会被擦除,这会导致 ModelMapper 无法识别目标 List 的实际泛型类型。
举个例子,下面这段代码不会生效:
List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
List<Character> characters = new ArrayList<>();
modelMapper.map(integers, characters);
结果你会发现 characters
是个空列表 —— 因为 JVM 在运行时只知道它是 List
,不知道应该是 List<Character>
。
✅ 正确做法:使用 TypeToken
创建类型字面量,保留泛型信息:
List<Character> characters = modelMapper.map(
integers,
new TypeToken<List<Character>>() {}.getType()
);
🔍 原理:通过匿名内部类的方式,JVM 能在编译期保留 <List<Character>>
这个类型信息,从而让 ModelMapper 正确解析目标类型。
3. 自定义泛型映射方法
实际开发中,我们经常需要把一个实体列表转成 DTO 列表。比如 List<User>
→ List<UserDTO>
。
最简单的做法是对每个元素单独映射:
List<UserDTO> dtos = users
.stream()
.map(user -> modelMapper.map(user, UserDTO.class))
.collect(Collectors.toList());
虽然能跑通,但重复代码太多。我们可以封装一个通用工具方法:
<S, T> List<T> mapList(List<S> source, Class<T> targetClass) {
return source
.stream()
.map(element -> modelMapper.map(element, targetClass))
.collect(Collectors.toList());
}
调用时就非常简洁了:
List<UserDTO> userDtoList = mapList(users, UserDTO.class);
✅ 推荐把这个方法抽到工具类中,项目里复用率极高。
4. TypeMap 与属性级映射
当需要更精细的控制时,比如只映射某个特定字段(如从 User
列表中提取用户名列表),可以使用 TypeMap
+ Converter
的组合拳。
假设我们有以下结构:
public class UserList {
private List<User> users;
// getter/setter
}
public class UserListDTO {
private List<String> usernames;
// getter/setter
}
目标是将 UserList.users
中每个用户的 username
提取出来,映射到 UserListDTO.usernames
。
步骤一:定义转换器
public class UsersListConverter extends AbstractConverter<List<User>, List<String>> {
@Override
protected List<String> convert(List<User> users) {
return users
.stream()
.map(User::getUsername)
.collect(Collectors.toList());
}
}
步骤二:注册 TypeMap 和属性映射
ModelMapper modelMapper = new ModelMapper();
TypeMap<UserList, UserListDTO> typeMap = modelMapper.createTypeMap(UserList.class, UserListDTO.class);
// 显式指定字段映射,并使用自定义转换器
typeMap.addMapping(
source -> source.getUsers(),
(destination, usernames) -> destination.setUsernames(usernames)
).setConverter(new UsersListConverter());
✅ 效果:users
列表 → usernames
字符串列表,一行代码搞定复杂逻辑。
⚠️ 注意:addMapping
的第一个参数是 source 的字段提取函数,第二个是 destination 的字段设置函数。
5. 总结
通过本文你应掌握以下三种 List 映射方式:
方式 | 适用场景 | 是否推荐 |
---|---|---|
TypeToken |
泛型 List 转换 | ✅ 强烈推荐 |
mapList 工具方法 |
实体转 DTO 列表 | ✅ 日常必备 |
TypeMap + Converter |
复杂字段映射 | ✅ 高级用法 |
ModelMapper 结合泛型、TypeToken 和自定义转换器,能够轻松应对各种对象列表映射需求,大幅减少手动转换的繁琐代码。
完整示例代码已托管至 GitHub:https://github.com/baeldung/tutorials/tree/master/core-java-modules/core-java-collections-conversions-2