Java对象—复制Clone
克隆条件
- 实现Clonyable接口(标记接口,自身没有方法。)
覆盖Object的clone()方法,默认可见性为protected,需要提升至public。
浅拷贝
对基本类型进行值传递,对数据类型进行引用传递。
- 不对clone()改造,只提升可见性是浅拷贝。
被复制对象若有引用对象属性,克隆后的对象的引用属性仍然指向被克隆对象。默认的clone方法仍然是赋值。
深拷贝
对基本类型值传递,对引用类型创建一个新的对象,并复制内容
需要引用类型属性的类也要实现clone。
@Datapublic class Address implements Cloneable {private String type;private String value;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}}
此外,被克隆对象还需要显事的clone其引用对象。
@Datapublic class Person implements Cloneable {private String name;private Integer age;private Address address;@Overrideprotected Object clone() throws CloneNotSupportedException {Object obj=super.clone();Address a=((Person)obj).getAddress();((Person)obj).setAddress((Address) a.clone());return obj;}}
利用序列化实现深拷贝
clone机制不是强类型的限制,比如实现了Cloneable并没有强制继承链上的对象也实现;也没有强制要求覆盖clone()方法。因此编码过程中比较容易忽略其中一个环节,对于复杂的项目排查就是困难了。
- 要寻找可靠的,简单的方法,序列化就是一种途径。
- 被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。
- 实现自己的deepClone方法,将this写入流,再读出来。俗称:冷冻-解冻。
Dozer拷贝
<dependency><groupId>net.sf.dozer</groupId><artifactId>dozer</artifactId><version>5.5.1</version></dependency>
@Test public void testDozer() { Person p1=PersonFactory.newPrototypeInstance(); Mapper mapper = new DozerBeanMapper(); Person p2 = mapper.map(p1, Person.class); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); }Commons-BeanUtils复制对象
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency>@Test public void testCommonsBeanUtils(){ Person p1=PersonFactory.newPrototypeInstance(); try { Person p2=(Person) BeanUtils.cloneBean(p1); System.out.println("p1=" + p1); p2.getAddress().setType("Office"); System.out.println("p2=" + p2); } catch (Exception e) { e.printStackTrace(); } }cglib复制对象
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency>@Test public void testCglib(){ Person p1=PersonFactory.newPrototypeInstance(); BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false); Person p2=new Person(); beanCopier.copy(p1, p2,null); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); }@Test public void testCglib(){ Person p1=PersonFactory.newPrototypeInstance(); BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true); Person p2=new Person(); beanCopier.copy(p1, p2, new Converter(){ @Override public Object convert(Object value, Class target, Object context) { if(target.isSynthetic()){ BeanCopier.create(target, target, true).copy(value, value, this); } return value; } }); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); }Orika复制对象
<dependency> <groupId>ma.glasnost.orika</groupId> <artifactId>orika-core</artifactId> <version>1.5.0</version> </dependency> </dependencies>@Test public void testOrika() { MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); mapperFactory.classMap(Person.class, Person.class) .byDefault() .register(); ConverterFactory converterFactory = mapperFactory.getConverterFactory(); MapperFacade mapper = mapperFactory.getMapperFacade(); Person p1=PersonFactory.newPrototypeInstance(); Person p2 = mapper.map(p1, Person.class); System.out.println("p1=" + p1); p2.getAddress().setType("Office"); System.out.println("p2=" + p2); }Spring BeanUtils复制对象
Person p1=PersonFactory.newPrototypeInstance(); Person p2 = new Person(); Person p2 = (Person) BeanUtils.cloneBean(p1);总结
原生的clone效率无疑是最高的,用脚趾头都能想到。偶尔用一次,用哪个都问题都不大。一般性能要求稍高的应用场景,cglib和orika完全可以接受。另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。
- 被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。
