Java 如何优雅的拷贝对象属性

场景

在 Java 项目中,经常遇到需要在对象之间拷贝属性的问题。然而,除了直接使用 Getter/Stter 方法,我们还有其他的方法么?
当然有,例如 Apache Common Lang3 的 BeanUtils,然而 BeanUtils 却无法完全满足吾辈的需求,所以吾辈便自己封装了一个,这里分享出来以供参考。

  • 需要大量复制对象的属性
  • 对象之间的属性名可能是不同的
  • 对象之间的属性类型可能是不同的

目标

简单易用的 API

  • copy: 指定需要拷贝的源对象和目标对象
  • prop: 拷贝指定对象的字段
  • props: 拷贝指定对象的多个字段
  • exec: 执行真正的拷贝操作
  • from: 重新开始添加其他对象的属性
  • get: 返回当前的目标对象
  • config: 配置拷贝的一些策略

思路

  • 定义门面类 BeanCopyUtil 用以暴露出一些 API
  • 定义每个字段的操作类 BeanCopyField,保存对每个字段的操作
  • 定义 BeanCopyConfig,用于配置拷贝属性的策略
  • 定义 BeanCopyOperator 作为拷贝的真正实现

图解

实现

注:反射部分依赖于 joor, JDK1.8 请使用 joor-java-8

定义门面类 BeanCopyUtil 用以暴露出一些 API

/**
 * java bean 复制操作的工具类
 *
 * @author rxliuli
 */
public class BeanCopyUtil<F, T> {
  /**
   * 源对象
   */
  private final F from;
  /**
   * 目标对象
   */
  private final T to;
  /**
   * 拷贝的字段信息列表
   */
  private final List<BeanCopyField> copyFieldList = new LinkedList<>();
  /**
   * 配置信息
   */
  private BeanCopyConfig config = new BeanCopyConfig();

  private BeanCopyUtil(F from, T to) {
    this.from = from;
    this.to = to;
  }

  /**
   * 指定需要拷贝的源对象和目标对象
   *
   * @param from 源对象
   * @param to  目标对象
   * @param <F> 源对象类型
   * @param <T> 目标对象类型
   * @return 一个 {@link BeanCopyUtil} 对象
   */
  public static <F, T> BeanCopyUtil<F, T> copy(F from, T to) {
    return new BeanCopyUtil<>(from, to);
  }

  /**
   * 拷贝指定对象的字段
   *
   * @param fromField 源对象中的字段名
   * @param toField  目标对象中的字段名
   * @param converter 将源对象中字段转换为目标对象字段类型的转换器
   * @return 返回 {@code this}
   */
  public BeanCopyUtil<F, T> prop(String fromField, String toField, Function<? super Object, ? super Object> converter) {
    copyFieldList.add(new BeanCopyField(fromField, toField, converter));
    return this;
  }

  /**
   * 拷贝指定对象的字段
   *
   * @param fromField 源对象中的字段名
   * @param toField  目标对象中的字段名
   * @return 返回 {@code this}
   */
  public BeanCopyUtil<F, T> prop(String fromField, String toField) {
    return prop(fromField, toField, null);
  }

  /**
   * 拷贝指定对象的字段
   *
   * @param field   源对象中与目标对象中的字段名
   * @param converter 将源对象中字段转换为目标对象字段类型的转换器
   * @return 返回 {@code this}
   */
  public BeanCopyUtil<F, T> prop(String field, Function<? super Object, ? super Object> converter) {
    return prop(field, field, converter);
  }

  /**
   * 拷贝指定对象的字段
   *
   * @param field 源对象中与目标对象中的字段名
   * @return 返回 {@code this}
   */
  public BeanCopyUtil<F, T> prop(String field) {
    return prop(field, field, null);
  }

  /**
   * 拷贝指定对象的多个字段
   *
   * @param fields 源对象中与目标对象中的多个字段名
   * @return 返回 {@code this}
   */
  public BeanCopyUtil<F, T> props(String... fields) {
    for (String field : fields) {
      prop(field);
    }
    return this;
  }

  /**
   * 执行真正的拷贝操作
   *
   * @return 返回 {@code this}
   */
  public BeanCopyUtil<F, T> exec() {
    new BeanCopyOperator<>(from, to, copyFieldList, config).copy();
    return this;
  }

  /**
   * 重新开始添加其他对象的属性
   * 用于在执行完 {@link #exec()} 之后还想复制其它对象的属性
   *
   * @param from 源对象
   * @param <R> 源对象类型
   * @return 一个新的 {@link BeanCopyUtil} 对象
   */
  public <R> BeanCopyUtil<R, T> from(R from) {
    return new BeanCopyUtil<>(from, to);
  }

  /**
   * 返回当前的目标对象
   *
   * @return 当前的目标对象
   */
  public T get() {
    return to;
  }

  /**
   * 配置拷贝的一些策略
   *
   * @param config 拷贝配置对象
   * @return 返回 {@code this}
   */
  public BeanCopyUtil<F, T> config(BeanCopyConfig config) {
    this.config = config;
    return this;
  }
}

定义每个字段的操作类 BeanCopyField,保存对每个字段的操作

/**
 * 拷贝属性的每一个字段的选项
 *
 * @author rxliuli
 */
public class BeanCopyField {
  private String from;
  private String to;
  private Function<? super Object, ? super Object> converter;

  public BeanCopyField() {
  }

  public BeanCopyField(String from, String to, Function<? super Object, ? super Object> converter) {
    this.from = from;
    this.to = to;
    this.converter = converter;
  }

  public String getFrom() {
    return from;
  }

  public BeanCopyField setFrom(String from) {
    this.from = from;
    return this;
  }

  public String getTo() {
    return to;
  }

  public BeanCopyField setTo(String to) {
    this.to = to;
    return this;
  }

  public Function<? super Object, ? super Object> getConverter() {
    return converter;
  }

  public BeanCopyField setConverter(Function<? super Object, ? super Object> converter) {
    this.converter = converter;
    return this;
  }
}

定义 BeanCopyConfig,用于配置拷贝属性的策略

/**
 * 拷贝属性的配置
 *
 * @author rxliuli
 */
public class BeanCopyConfig {
  /**
   * 同名的字段自动复制
   */
  private boolean same = true;
  /**
   * 覆盖同名的字段
   */
  private boolean override = true;
  /**
   * 忽略 {@code null} 的源对象属性
   */
  private boolean ignoreNull = true;
  /**
   * 尝试进行自动转换
   */
  private boolean converter = true;

  public BeanCopyConfig() {
  }

  public BeanCopyConfig(boolean same, boolean override, boolean ignoreNull, boolean converter) {
    this.same = same;
    this.override = override;
    this.ignoreNull = ignoreNull;
    this.converter = converter;
  }

  public boolean isSame() {
    return same;
  }

  public BeanCopyConfig setSame(boolean same) {
    this.same = same;
    return this;
  }

  public boolean isOverride() {
    return override;
  }

  public BeanCopyConfig setOverride(boolean override) {
    this.override = override;
    return this;
  }

  public boolean isIgnoreNull() {
    return ignoreNull;
  }

  public BeanCopyConfig setIgnoreNull(boolean ignoreNull) {
    this.ignoreNull = ignoreNull;
    return this;
  }

  public boolean isConverter() {
    return converter;
  }

  public BeanCopyConfig setConverter(boolean converter) {
    this.converter = converter;
    return this;
  }
}

定义 BeanCopyOperator 作为拷贝的真正实现

/**
 * 真正执行 copy 属性的类
 *
 * @author rxliuli
 */
public class BeanCopyOperator<F, T> {
  private static final Logger log = LoggerFactory.getLogger(BeanCopyUtil.class);
  private final F from;
  private final T to;
  private final BeanCopyConfig config;
  private List<BeanCopyField> copyFieldList;

  public BeanCopyOperator(F from, T to, List<BeanCopyField> copyFieldList, BeanCopyConfig config) {
    this.from = from;
    this.to = to;
    this.copyFieldList = copyFieldList;
    this.config = config;
  }

  public void copy() {
    //获取到两个对象所有的属性
    final Map<String, Reflect> fromFields = Reflect.on(from).fields();
    final Reflect to = Reflect.on(this.to);
    final Map<String, Reflect> toFields = to.fields();
    //过滤出所有相同字段名的字段并进行拷贝
    if (config.isSame()) {
      final Map<ListUtil.ListDiffState, List<String>> different = ListUtil.different(new ArrayList<>(fromFields.keySet()), new ArrayList<>(toFields.keySet()));
      copyFieldList = Stream.concat(different.get(ListUtil.ListDiffState.common).stream()
          .map(s -> new BeanCopyField(s, s, null)), copyFieldList.stream())
          .collect(Collectors.toList());
    }
    //根据拷贝字段列表进行拷贝
    copyFieldList.stream()
        //忽略空值
        .filter(beanCopyField -> !config.isIgnoreNull() || fromFields.get(beanCopyField.getFrom()).get() != null)
        //覆盖属性
        .filter(beanCopyField -> config.isOverride() || toFields.get(beanCopyField.getTo()).get() == null)
        //如果没有转换器,则使用默认的转换器
        .peek(beanCopyField -> {
          if (beanCopyField.getConverter() == null) {
            beanCopyField.setConverter(Function.identity());
          }
        })
        .forEach(beanCopyField -> {
          final String fromField = beanCopyField.getFrom();
          final F from = fromFields.get(fromField).get();
          final String toField = beanCopyField.getTo();
          try {
            to.set(toField, beanCopyField.getConverter().apply(from));
          } catch (ReflectException e) {
            log.warn("Copy field failed, from {} to {}, exception is {}", fromField, toField, e.getMessage());
          }
        });
  }
}

使用

使用流程图

测试

代码写完了,让我们测试一下!

public class BeanCopyUtilTest {
  private final Logger log = LoggerFactory.getLogger(getClass());
  private Student student;
  private Teacher teacher;

  @Before
  public void before() {
    student = new Student("琉璃", 10, "女", 4);
    teacher = new Teacher();
  }

  @Test
  public void copy() {
    //简单的复制(类似于 BeanUtils.copyProperties)
    BeanCopyUtil.copy(student, teacher).exec();
    log.info("teacher: {}", teacher);
    assertThat(teacher)
        .extracting("age")
        .containsOnlyOnce(student.getAge());
  }

  @Test
  public void prop() {
    //不同名字的属性
    BeanCopyUtil.copy(student, teacher)
        .prop("sex", "sex", sex -> Objects.equals(sex, "男"))
        .prop("realname", "name")
        .exec();
    assertThat(teacher)
        .extracting("name", "age", "sex")
        .containsOnlyOnce(student.getRealname(), student.getAge(), false);
  }

  @Test
  public void prop1() {
    //不存的属性
    assertThat(BeanCopyUtil.copy(student, teacher)
        .prop("sex", "sex", sex -> Objects.equals(sex, "男"))
        .prop("realname", "name2")
        .exec()
        .get())
        .extracting("age", "sex")
        .containsOnlyOnce(student.getAge(), false);
  }

  @Test
  public void from() {
    final Teacher lingMeng = new Teacher()
        .setName("灵梦")
        .setAge(17);
    //测试 from 是否覆盖
    assertThat(BeanCopyUtil.copy(student, teacher)
        .prop("sex", "sex", sex -> Objects.equals(sex, "男"))
        .prop("realname", "name")
        .exec()
        .from(lingMeng)
        .exec()
        .get())
        .extracting("name", "age", "sex")
        .containsOnlyOnce(lingMeng.getName(), lingMeng.getAge(), false);
  }

  @Test
  public void get() {
    //测试 get 是否有效
    assertThat(BeanCopyUtil.copy(student, teacher)
        .prop("sex", "sex", sex -> Objects.equals(sex, "男"))
        .prop("realname", "name")
        .exec()
        .get())
        .extracting("name", "age", "sex")
        .containsOnlyOnce(student.getRealname(), student.getAge(), false);
  }

  @Test
  public void config() {
    //不自动复制同名属性
    assertThat(BeanCopyUtil.copy(new Student().setAge(15), new Teacher())
        .config(new BeanCopyConfig().setSame(false))
        .exec()
        .get())
        .extracting("age")
        .containsOnlyNulls();
    //不覆盖不为空的属性
    assertThat(BeanCopyUtil.copy(new Student().setAge(15), new Teacher().setAge(10))
        .config(new BeanCopyConfig().setOverride(false))
        .exec()
        .get())
        .extracting("age")
        .containsOnlyOnce(10);
    //不忽略源对象不为空的属性
    assertThat(BeanCopyUtil.copy(new Student(), student)
        .config(new BeanCopyConfig().setIgnoreNull(false))
        .exec()
        .get())
        .extracting("realname", "age", "sex", "grade")
        .containsOnlyNulls();
  }

  /**
   * 测试学生类
   */
  private static class Student {
    /**
     * 姓名
     */
    private String realname;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别,男/女
     */
    private String sex;
    /**
     * 年级,1 - 6
     */
    private Integer grade;

    public Student() {
    }

    public Student(String realname, Integer age, String sex, Integer grade) {
      this.realname = realname;
      this.age = age;
      this.sex = sex;
      this.grade = grade;
    }

    public String getRealname() {

      return realname;
    }

    public Student setRealname(String realname) {
      this.realname = realname;
      return this;
    }

    public Integer getAge() {
      return age;
    }

    public Student setAge(Integer age) {
      this.age = age;
      return this;
    }

    public String getSex() {
      return sex;
    }

    public Student setSex(String sex) {
      this.sex = sex;
      return this;
    }

    public Integer getGrade() {
      return grade;
    }

    public Student setGrade(Integer grade) {
      this.grade = grade;
      return this;
    }

    @Override
    public String toString() {
      return ToStringBuilder.reflectionToString(this);
    }
  }

  /**
   * 测试教师类
   */
  private static class Teacher {
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别,true 男,false 女
     */
    private Boolean sex;
    /**
     * 职位
     */
    private String post;

    public Teacher() {
    }

    public Teacher(String name, Integer age, Boolean sex, String post) {
      this.name = name;
      this.age = age;
      this.sex = sex;
      this.post = post;
    }

    public String getName() {
      return name;
    }

    public Teacher setName(String name) {
      this.name = name;
      return this;
    }

    public Integer getAge() {
      return age;
    }

    public Teacher setAge(Integer age) {
      this.age = age;
      return this;
    }

    public Boolean getSex() {
      return sex;
    }

    public Teacher setSex(Boolean sex) {
      this.sex = sex;
      return this;
    }

    public String getPost() {
      return post;
    }

    public Teacher setPost(String post) {
      this.post = post;
      return this;
    }

    @Override
    public String toString() {
      return ToStringBuilder.reflectionToString(this);
    }
  }
}

如果没有发生什么意外,那么一切将能够正常运行!

好了,那么关于在 Java 中优雅的拷贝对象属性就到这里啦

以上就是Java 如何优雅的拷贝对象属性的详细内容,更多关于Java 拷贝对象属性的资料请关注我们其它相关文章!

(0)

相关推荐

  • java ArrayList集合中的某个对象属性进行排序的实现代码

    开发中有时候需要自己封装分页排序时,List如何对某一属性排序呢,分享一个小实例,大家共勉,希望能对大家有用,请多多指教. 1.Student的Bean如下: public class Student { private int age; private String name; private String weight; public String getWeight() { return weight; } public void setWeight(String weight) { th

  • 基于java中两个对象属性的比较

    两个对象进行比较相等,有两种做法: 1.情况一:当仅仅只是判断两个对象是否相等时,只需重写equals()方法即可.这里就不用说明 2.情况二:当除了情况一之外,还需知道是那个属性不同,那么就需要采用类反射, 具体代码如下: public static void main(String[] args) { A a = new A(); a.setUserName("a"); a.setPassword("p"); a.setQq("q"); a.

  • Java如何获取对象属性及对应值

    利用反射获取对象的所有属性及对应的值 1.获取属性名数组 private static String[] getFiledName(Object o) { Field[] fields = o.getClass().getDeclaredFields(); String[] fieldNames = new String[fields.length]; for (int i = 0; i < fields.length; i++) { fieldNames[i] = fields[i].getN

  • java基于反射得到对象属性值的方法

    本文实例讲述了java基于反射得到对象属性值的方法.分享给大家供大家参考,具体如下: 通过反射机制得到对象中的属性和属性值 在对象中private没问题,在别的类中有时会报异常.下面的例子是在本对象中 /** * Engine entity. @author MyEclipse Persistence Tools */ public class Engine implements java.io.Serializable { // Fields private Long engineId; pr

  • java根据List内对象的属性排序方法

    方法一:实现Comparator接口,并重写compare方法 实体类代码: import java.util.Comparator; /** * 学生类 方法一 * 实现Comparator接口 * 并重写compare方法 * @author liaot * */ public class Student implements Comparator<Student>{ private String name; //姓名 private int age; //年龄 //重写 比较方法 本次例

  • Java如何基于反射获取对象属性信息

    先建立一个类,有四种属性: private int id; private String name; private byte by; private short st; 以下方法,创建一个对象,然后打印该对象的属性名字,属性值,和属性的类型: public class T { public static void main(String[] args) throws Exception { User u = new User(); u.setId(1); u.setName("cc"

  • Java中List集合对象去重及按属性去重的8种方法

    最近在写一些关于java基础的文章,但是我又不想按照教科书的方式去写知识点的文章,因为意义不大.基础知识太多了,如何将这些知识归纳总结,总结出优缺点或者是使用场景才是对知识的升华.所以我更想把java相关的基础知识进行穿针引线,进行整体上的总结. 总结java中创建并写文件的5种方式 总结java从文件中读取数据的6种方法 总结java创建文件夹的4种方法及其优缺点 总结java中删除文件或文件夹的7种方法 总结java中文件拷贝剪切的5种方式 比如之前我已经写了上面的这些内容,如果对java基

  • Java对象转JSON时动态的增删改查属性详解

    1. 前言 日常开发中少不了JSON处理,少不了需要在JSON中添加额外字段或者删除特定字段的需求.今天我们就使用Jackson类库来实现这个功能. 2. JSON字符串增加额外字段 假如我们有这样结构的JSON: { "username":"felord.cn", "age":18 } 期望增加一个性别字段gender: { "username": "felord.cn", "age"

  • Java通过反射机制动态设置对象属性值的方法

    /** * MethodName: getReflection<br> * Description:解析respXML 在通过反射设置对象属性值 * User: liqijing * Date:2015-7-19下午12:42:55 * @param clzzName * @param respXML * @return * @throws ClassNotFoundException * @throws DocumentException * @throws IllegalArgumentE

  • java jackson 将对象转json时,忽略子对象的某个属性操作

    我就废话不多说了,大家还是直接看代码吧~ //父对象 public class user implements java.io.Serializable { @JsonIgnoreProperties(value={"addressId"})//在解析成json时,忽略子属性的addressId字段 private Address address; private String username; //......... } //子对象 public class Address imp

随机推荐