Java对象—复制Clone
克隆条件
- 实现Clonyable接口(标记接口,自身没有方法。)
覆盖Object的clone()方法,默认可见性为protected,需要提升至public。
浅拷贝
对基本类型进行值传递,对数据类型进行引用传递。
- 不对clone()改造,只提升可见性是浅拷贝。
被复制对象若有引用对象属性,克隆后的对象的引用属性仍然指向被克隆对象。默认的clone方法仍然是赋值。
深拷贝
对基本类型值传递,对引用类型创建一个新的对象,并复制内容
需要引用类型属性的类也要实现clone。
@Data
public class Address implements Cloneable {
private String type;
private String value;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
此外,被克隆对象还需要显事的clone其引用对象。
@Data
public class Person implements Cloneable {
private String name;
private Integer age;
private Address address;
@Override
protected 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的要求不强制,对深拷贝来说没毛病。