Java中是否存在简洁方式,用同类型对象的值替换目标对象的所有属性值?
我太懂你这种烦恼了——当项目规模上来,实体类属性越加越多,手动写一堆setter不仅繁琐到爆炸,后续维护还容易漏改,完全是样板代码的噩梦!
先回顾下你提到的初始方案:
一开始我们可能会这么写,找到要更新的对象后逐个调用setter:
public void updateStudent(Long id, Student newStudent) { Student studentToBeUpdated = studentRepo .findStudentById(id) .orElseThrow(() -> new UserNotFoundException("Student with id " + id + " was not found")); studentToBeUpdated.setFirstName(newStudent.getFirstName()); studentToBeUpdated.setLastName(newStudent.getLastName()); studentToBeUpdated.setEmail(newStudent.getEmail()); }
但一旦Student类新增20个属性,就得同步更新这个方法,维护成本直接拉满,完全不优雅。
你想要的studentToBeUpdated.replace(newStudent)这种写法绝对可行!下面给你几个实用的解决方案:
方案1:Spring BeanUtils 一键搞定(最快捷)
Spring自带的BeanUtils.copyProperties就能实现你想要的效果,它会自动把源对象的属性值拷贝到目标对象,只要属性名和类型匹配就生效:
import org.springframework.beans.BeanUtils; public void updateStudent(Long id, Student newStudent) { Student studentToBeUpdated = studentRepo .findStudentById(id) .orElseThrow(() -> new UserNotFoundException("Student with id " + id + " was not found")); // 一行代码完成属性拷贝,完美替代一堆setter BeanUtils.copyProperties(newStudent, studentToBeUpdated); }
⚠️ 小提醒:默认情况下,这个方法会把源对象的null值也拷贝过去,如果不想覆盖目标对象的非null属性,可以加个辅助方法过滤null属性:
BeanUtils.copyProperties(newStudent, studentToBeUpdated, getNullPropertyNames(newStudent)); // 辅助方法:获取源对象中值为null的属性名 private String[] getNullPropertyNames(Object source) { final BeanWrapper src = new BeanWrapperImpl(source); java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors(); Set<String> emptyNames = new HashSet<>(); for(java.beans.PropertyDescriptor pd : pds) { Object srcValue = src.getPropertyValue(pd.getName()); if (srcValue == null) emptyNames.add(pd.getName()); } return emptyNames.toArray(new String[0]); }
方案2:自己封装通用replace方法(无框架依赖)
如果你的项目没用到Spring,也可以用反射写一个通用的属性拷贝工具,这样不管什么类都能直接用replace:
public static <T> void replace(T target, T source) throws IllegalAccessException { Class<?> clazz = target.getClass(); // 获取类的所有属性(包括私有属性) Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Object value = field.get(source); // 可选:只拷贝非null值,避免覆盖目标对象已有数据 if (value != null) { field.set(target, value); } } }
调用的时候就完全符合你的预期:
replace(studentToBeUpdated, newStudent);
⚠️ 注意:反射会有一点点性能损耗,但在绝大多数业务场景下完全可以忽略。如果是高性能要求的场景,可以考虑用ASM或CGLIB这类字节码库优化。
方案3:MapStruct 类型安全+高性能(进阶之选)
如果追求类型安全和编译期检查,MapStruct是绝佳选择。它会在编译期生成属性拷贝的实现类,没有反射的性能问题,还能处理复杂类型转换:
首先添加Maven依赖:
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.5.Final</version> <scope>provided</scope> </dependency>
然后定义一个Mapper接口:
@Mapper(componentModel = "spring") public interface StudentMapper { // @MappingTarget 标记要更新的目标对象 void updateStudentFromSource(Student source, @MappingTarget Student target); }
最后在Service里注入调用:
@Autowired private StudentMapper studentMapper; public void updateStudent(Long id, Student newStudent) { Student studentToBeUpdated = studentRepo .findStudentById(id) .orElseThrow(() -> new UserNotFoundException("Student with id " + id + " was not found")); studentMapper.updateStudentFromSource(newStudent, studentToBeUpdated); }
MapStruct会自动生成实现类,帮你处理属性拷贝,而且如果属性名不匹配或类型不兼容,编译期就会报错,非常安全可靠。
总结
- 快速实现选Spring BeanUtils,省事又省心;
- 无框架依赖就自己写反射工具类;
- 追求类型安全和高性能选MapStruct。
内容的提问来源于stack exchange,提问作者MegaKruk

