java 属性拷贝_Java 如何优雅的拷贝对象属性

本文介绍了一种在Java项目中优雅地复制对象属性的方法,通过自定义工具类`BeanCopyUtil`,实现了灵活的属性拷贝功能,包括处理不同名称和类型的字段,以及配置拷贝策略。同时,文章提供了详细的测试用例来验证其实现。
摘要由CSDN通过智能技术生成

场景

在 Java 项目中,经常遇到需要在对象之间拷贝属性的问题。然而,除了直接使用 Getter/Stter 方法,我们还有其他的方法么?

当然有,例如 Apache Common Lang3 的 BeanUtils,然而 BeanUtils 却无法完全满足吾辈的需求,所以吾辈便自己封装了一个,这里分享出来以供参考。

需要大量复制对象的属性

对象之间的属性名可能是不同的

对象之间的属性类型可能是不同的

目标

简单易用的 API

copy: 指定需要拷贝的源对象和目标对象

prop: 拷贝指定对象的字段

props: 拷贝指定对象的多个字段

exec: 执行真正的拷贝操作

from: 重新开始添加其他对象的属性

get: 返回当前的目标对象

config: 配置拷贝的一些策略

思路

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

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

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

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

图解

ebb9ac21255a0b93540bb07c040e40cb.png

实现

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

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

/**

* java bean 复制操作的工具类

*

* @author rxliuli

*/

public class BeanCopyUtil {

/**

* 源对象

*/

private final F from;

/**

* 目标对象

*/

private final T to;

/**

* 拷贝的字段信息列表

*/

private final List 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 源对象类型

* @param 目标对象类型

* @return 一个 {@link BeanCopyUtil} 对象

*/

public static BeanCopyUtil copy(F from, T to) {

return new BeanCopyUtil<>(from, to);

}

/**

* 拷贝指定对象的字段

*

* @param fromField 源对象中的字段名

* @param toField 目标对象中的字段名

* @param converter 将源对象中字段转换为目标对象字段类型的转换器

* @return 返回 {@code this}

*/

public BeanCopyUtil 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 prop(String fromField, String toField) {

return prop(fromField, toField, null);

}

/**

* 拷贝指定对象的字段

*

* @param field 源对象中与目标对象中的字段名

* @param converter 将源对象中字段转换为目标对象字段类型的转换器

* @return 返回 {@code this}

*/

public BeanCopyUtil prop(String field, Function super Object, ? super Object> converter) {

return prop(field, field, converter);

}

/**

* 拷贝指定对象的字段

*

* @param field 源对象中与目标对象中的字段名

* @return 返回 {@code this}

*/

public BeanCopyUtil prop(String field) {

return prop(field, field, null);

}

/**

* 拷贝指定对象的多个字段

*

* @param fields 源对象中与目标对象中的多个字段名

* @return 返回 {@code this}

*/

public BeanCopyUtil props(String... fields) {

for (String field : fields) {

prop(field);

}

return this;

}

/**

* 执行真正的拷贝操作

*

* @return 返回 {@code this}

*/

public BeanCopyUtil exec() {

new BeanCopyOperator<>(from, to, copyFieldList, config).copy();

return this;

}

/**

* 重新开始添加其他对象的属性

* 用于在执行完 {@link #exec()} 之后还想复制其它对象的属性

*

* @param from 源对象

* @param 源对象类型

* @return 一个新的 {@link BeanCopyUtil} 对象

*/

public BeanCopyUtil from(R from) {

return new BeanCopyUtil<>(from, to);

}

/**

* 返回当前的目标对象

*

* @return 当前的目标对象

*/

public T get() {

return to;

}

/**

* 配置拷贝的一些策略

*

* @param config 拷贝配置对象

* @return 返回 {@code this}

*/

public BeanCopyUtil 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 {

private static final Logger log = LoggerFactory.getLogger(BeanCopyUtil.class);

private final F from;

private final T to;

private final BeanCopyConfig config;

private List copyFieldList;

public BeanCopyOperator(F from, T to, List copyFieldList, BeanCopyConfig config) {

this.from = from;

this.to = to;

this.copyFieldList = copyFieldList;

this.config = config;

}

public void copy() {

//获取到两个对象所有的属性

final Map fromFields = Reflect.on(from).fields();

final Reflect to = Reflect.on(this.to);

final Map toFields = to.fields();

//过滤出所有相同字段名的字段并进行拷贝

if (config.isSame()) {

final Map> 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());

}

});

}

}

使用

使用流程图

ef592a04881431c3a84d37c0e58ccbed.png

测试

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

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 拷贝对象属性的资料请关注脚本之家其它相关文章!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值