为何MapStruct的@BeforeMapping未返回修改后的列表?
问题
我尝试用MapStruct把UserDTO列表里的非空元素映射成User列表。虽然单个的toEntity(UserDTO userDTO)方法会校验空值,但集合映射生成的列表还是保留了原DTO列表的长度(可能包含空元素)。于是我写了下面的@BeforeMapping方法:
@BeforeMapping default void filterNonNullDTO(List<UserDTO> userDTO){ userDTO=userDTO.stream().filter(Objects::nonNull).collect(Collectors.toList()); }
但MapStruct生成的实现代码是这样的:
@Override public List<User> toEntities(List<UserDTO> userDtos) { filterNonNullDTO( userDtos ); if ( userDtos == null ) { return null; } List<User> list = new ArrayList<User>( userDtos.size() ); for ( UserDTO userDTO : userDtos ) { list.add( toEntity( userDTO ) ); } return list; }
我写了JUnit5测试用例验证逻辑,但没通过:预期列表长度为1,实际是2。测试代码如下:
@Test void mapUserDTOsToUserShouldReturnNonNullUsers(){ List<AdminUserDTO> userDTOs= new ArrayList<>(); userDTOs.add(userDto); userDTOs.add(null); List<User> users= userMapper.toEntities(userDTOs); assertThat(users).isNotEmpty().size().isEqualTo(1); }
测试结果:expected: 1 but was: 2,请问我的代码哪里有问题?
问题根源
你的filterNonNullDTO方法犯了Java参数传递的典型错误:Java是值传递,你在方法里重新赋值userDTO变量,只是改变了方法内部的局部变量引用,并不会修改原列表的内容或者外部传入的userDtos引用。
生成的代码里调用filterNonNullDTO(userDtos)后,原userDtos列表根本没被修改,还是原来包含一个非空元素和一个null的列表,所以后续遍历的时候依然会循环2次,最终生成长度为2的User列表(其中一个是toEntity(null)返回的null)。
解决方案
你需要直接修改传入的列表,而不是重新赋值局部变量。可以用removeIf方法直接移除列表中的null元素:
@BeforeMapping default void filterNonNullDTO(List<UserDTO> userDTOs){ if (userDTOs != null) { userDTOs.removeIf(Objects::isNull); } }
这样调用这个方法后,原列表里的null元素会被直接移除,后续MapStruct遍历的时候就只会处理剩下的非空元素,最终生成长度符合预期的User列表。
内容的提问来源于stack exchange,提问作者Islam
相关产品推荐
相关产品推荐

