前几天写代码的时候,需要拷贝对象,就使用了org.apache.commons.beanutils.BeanUtils
的BeanUtils.copyProperties(Object dest, Object orig)
拷贝对象,后面又修改了新对象的属性,就导致原对象也被修改了,仔细一研究才发现这个工具只是进行了浅拷贝。索性整理一下现在比较常用的一些深拷贝和浅拷贝工具。
我的博客:深拷贝和浅拷贝工具
深拷贝
1. Orika的MapperFactory
Orika底层采用了javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件,因此在速度上比使用反射进行赋值会快很多。线程安全,可以使用单例。推荐!
首先引入依赖:
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.2</version>
</dependency>
可以拷贝单个对象,也可以拷贝列表,这里只介绍单个对象的拷贝:
直接克隆对象
克隆的对象可以不同,深拷贝两个对象相同的属性,跳过不同的属性。
@Test public void orikaClone() { MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); MapperFacade mapperFacade = mapperFactory.getMapperFacade(); DemoObj fromObj = new DemoObj(); fromObj.setStr("a"); fromObj.setList(Lists.newArrayList("A", "B")); DemoObj toObj = mapperFacade.map(fromObj, DemoObj.class); toObj.setStr("b"); toObj.getList().add("C"); System.out.println(JSON.toJSONString(fromObj)); System.out.println(JSON.toJSONString(toObj)); } //输出: //{"list":["A","B"],"str":"a"} //{"list":["A","B","C"],"str":"b"}
拷贝对象属性
克隆的对象可以不同,深拷贝两个对象相同的属性,跳过不同的属性。
@Test public void orikaCopy() { MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); MapperFacade mapperFacade = mapperFactory.getMapperFacade(); DemoObj fromObj = new DemoObj(); fromObj.setStr("a"); fromObj.setList(Lists.newArrayList("A", "B")); DemoObj toObj = new DemoObj(); mapperFacade.map(fromObj, toObj); toObj.setStr("b"); toObj.getList().add("C"); System.out.println(JSON.toJSONString(fromObj)); System.out.println(JSON.toJSONString(toObj)); } //输出: //{"list":["A","B"],"str":"a"} //{"list":["A","B","C"],"str":"b"}
Dozer的DozerBeanMapper
dozer是一种JavaBean的映射工具,类似于apache的BeanUtils。但是dozer更强大,它可以灵活的处理复杂类型之间的映射。不但可以进行简单的属性映射、复杂的类型映射、双向映射、递归映射等,并且可以通过XML配置文件进行灵活的配置。 线程安全,可以使用单例,性能一般。
引入依赖:
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.5.1</version>
</dependency>
深拷贝属性,对象可以不同,属性不同时跳过。
@Test
public void dozerCopy() {
DozerBeanMapper dozer = new DozerBeanMapper();
DemoObj fromObj = new DemoObj();
fromObj.setStr("a");
fromObj.setList(Lists.newArrayList("A", "B"));
DemoObj2 toObj = new DemoObj2();
dozer.map(fromObj, toObj);
toObj.setStr("b");
toObj.getList().add("C");
System.out.println(JSON.toJSONString(fromObj));
System.out.println(JSON.toJSONString(toObj));
}
//输出:
//{"list":["A","B"],"str":"a"}
//{"list":["A","B","C"],"str":"b"}
利用序列化实现深拷贝
利用输入输出流,将旧对象写入到新对象,实现拷贝。性能低,不推荐。
@Data
public static class DeepCloneDemo implements Serializable {
String str;
List<String> list;
public DeepCloneDemo deepClone() {
DeepCloneDemo to = null;
DeepCloneDemo from = this;
PipedOutputStream out = new PipedOutputStream();
PipedInputStream in = new PipedInputStream();
try {
in.connect(out);
} catch (IOException e) {
e.printStackTrace();
}
try (ObjectOutputStream bo = new ObjectOutputStream(out);
ObjectInputStream bi = new ObjectInputStream(in);) {
bo.writeObject(from);
to = (DeepCloneDemo) bi.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return to;
}
}
@Test
public void deepClone() {
DeepCloneDemo from = new DeepCloneDemo();
from.setStr("a");
from.setList(Lists.newArrayList("A", "B"));
DeepCloneDemo to = from.deepClone();
to.setStr("b");
to.getList().add("C");
System.out.println(JSON.toJSONString(from));
System.out.println(JSON.toJSONString(to));
}
//输出:
//{"list":["A","B"],"str":"a"}
//{"list":["A","B","C"],"str":"b"}
浅拷贝
1. apache的BeanUtils
所处的包:org.apache.commons.beanutils.BeanUtils
基于反射拷贝,提供了两个拷贝对象的方法:
BeanUtils.cloneBean(final Object bean)
传入一个对象,浅拷贝生成一个新的对象。
@Test public void apacheClone() throws Exception { DemoObj fromObj = new DemoObj(); fromObj.setS("a"); fromObj.setL(Lists.newArrayList("A", "B")); DemoObj toObj = (DemoObj) org.apache.commons.beanutils.BeanUtils.cloneBean(fromObj); toObj.setStr("b"); toObj.getList().add("C"); System.out.println(JSON.toJSONString(fromObj)); System.out.println(JSON.toJSONString(toObj)); } //输出: //{"list":["A","B","C"],"str":"a"} //{"list":["A","B","C"],"str":"b"}
BeanUtils.copyProperties(final Object dest, final Object orig)
传入两个对象,浅拷贝相同的属性的值。两个对象的类型可以不一样,属性及属性类型相同即可,忽略不同的属性。
@Test public void apacheCopy() throws Exception { DemoObj fromObj = new DemoObj(); fromObj.setS("a"); fromObj.setL(Lists.newArrayList("A", "B")); DemoObj toObj = new DemoObj(); org.apache.commons.beanutils.BeanUtils.copyProperties(toObj, fromObj); toObj.setStr("b"); toObj.getList().add("C"); System.out.println(JSON.toJSONString(fromObj)); System.out.println(JSON.toJSONString(toObj)); } //输出: //{"list":["A","B","C"],"str":"a"} //{"list":["A","B","C"],"str":"b"}
2. springframework的BeanUtils
所处的包:org.springframework.beans.BeanUtils
提供了四个拷贝对象的方法,基本原理都一样,只不过是做了一些定制。这里只整理基本的拷贝方法:
BeanUtils.copyProperties(Object source, Object target)
功能与apache的相似,需要注意的是,传入对象的顺序刚好相反,原对象在第一个,新对象在第二个
@Test public void springCopy() { DemoObj fromObj = new DemoObj(); fromObj.setStr("a"); fromObj.setList(Lists.newArrayList("A", "B")); DemoObj toObj = new DemoObj(); org.springframework.beans.BeanUtils.copyProperties(fromObj, toObj); toObj.setStr("b"); toObj.getList().add("C"); System.out.println(JSON.toJSONString(fromObj)); System.out.println(JSON.toJSONString(toObj)); } //输出: //{"list":["A","B","C"],"str":"a"} //{"list":["A","B","C"],"str":"b"}