You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 16:47:31