java对象拷贝详解及实例
java对象拷贝详解及实例
java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的:
@test public void testassign(){ person p1=new person(); p1.setage(31); p1.setname("peter"); person p2=p1; system.out.println(p1==p2);//true }
如果创建一个对象的新的副本,也就是说他们的初始状态完全一样,但以后可以改变各自的状态,而互不影响,就需要用到java中对象的复制,如原生的clone()方法。
如何进行对象克隆
object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的,所以实体类使用克隆的前提是:
① 实现cloneable接口,这是一个标记接口,自身没有方法。
② 覆盖clone()方法,可见性提升为public。
@data public class person implements cloneable { private string name; private integer age; private address address; @override protected object clone() throws clonenotsupportedexception { return super.clone(); } } @test public void testshallowcopy() throws exception{ person p1=new person(); p1.setage(31); p1.setname("peter"); person p2=(person) p1.clone(); system.out.println(p1==p2);//false p2.setname("jacky"); system.out.println("p1="+p1);//p1=person [name=peter, age=31] system.out.println("p2="+p2);//p2=person [name=jacky, age=31] }
该测试用例只有两个基本类型的成员,测试达到目的了。
事情貌似没有这么简单,为person增加一个address类的成员:
@data public class address { private string type; private string value; }
再来测试,问题来了。
@test public void testshallowcopy() throws exception{ address address=new address(); address.settype("home"); address.setvalue("北京"); person p1=new person(); p1.setage(31); p1.setname("peter"); p1.setaddress(address); person p2=(person) p1.clone(); system.out.println(p1==p2);//false p2.getaddress().settype("office"); system.out.println("p1="+p1); system.out.println("p2="+p2); }
查看输出:
false p1=person(name=peter, age=31, address=address(type=office, value=北京)) p2=person(name=peter, age=31, address=address(type=office, value=北京))
遇到了点麻烦,只修改了p2的地址类型,两个地址类型都变成了office。
浅拷贝和深拷贝
前面实例中是浅拷贝和深拷贝的典型用例。
浅拷贝:被复制对象的所有值属性都含有与原来对象的相同,而所有的对象引用属性仍然指向原来的对象。
深拷贝:在浅拷贝的基础上,所有引用其他对象的变量也进行了clone,并指向被复制过的新对象。
也就是说,一个默认的clone()方法实现机制,仍然是赋值。
如果一个被复制的属性都是基本类型,那么只需要实现当前类的cloneable机制就可以了,此为浅拷贝。
如果被复制对象的属性包含其他实体类对象引用,那么这些实体类对象都需要实现cloneable接口并覆盖clone()方法。
@data public class address implements cloneable { private string type; private string value; @override protected object clone() throws clonenotsupportedexception { return super.clone(); } }
这样还不够,person的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; } }
重新跑前面的测试用例:
false p1=person(name=peter, age=31, address=address(type=home, value=北京)) p2=person(name=peter, age=31, address=address(type=office, value=北京))
clone方式深拷贝小结
① 如果有一个非原生成员,如自定义对象的成员,那么就需要:
- 该成员实现cloneable接口并覆盖clone()方法,不要忘记提升为public可见。
- 同时,修改被复制类的clone()方法,增加成员的克隆逻辑。
② 如果被复制对象不是直接继承object,中间还有其它继承层次,每一层super类都需要实现cloneable接口并覆盖clone()方法。
与对象成员不同,继承关系中的clone不需要被复制类的clone()做多余的工作。
一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。
前面的实例还可以接受,如果有n个对象成员,有m层继承关系,就会很麻烦。
利用序列化实现深拷贝
clone机制不是强类型的限制,比如实现了cloneable并没有强制继承链上的对象也实现;也没有强制要求覆盖clone()方法。因此编码过程中比较容易忽略其中一个环节,对于复杂的项目排查就是困难了。
要寻找可靠的,简单的方法,序列化就是一种途径。
1.被复制对象的继承链、引用链上的每一个对象都实现java.io.serializable接口。这个比较简单,不需要实现任何方法,serialversionid的要求不强制,对深拷贝来说没毛病。
2.实现自己的deepclone方法,将this写入流,再读出来。俗称:冷冻-解冻。
@data public class person implements serializable { private string name; private integer age; private address address; public person deepclone() { person p2=null; person p1=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(p1); p2=(person) bi.readobject(); } catch (exception e) { e.printstacktrace(); } return p2; } }
原型工厂类
为了便于测试,也节省篇幅,封装一个工厂类。
公平起见,避免某些工具库使用缓存机制,使用原型方式工厂。
public class personfactory{ public static person newprototypeinstance(){ address address = new address(); address.settype("home"); address.setvalue("北京"); person p1 = new person(); p1.setaddress(address); p1.setage(31); p1.setname("peter"); return p1; } }
利用dozer拷贝对象
dozer是一个bean处理类库。
maven依赖
<dependency> <groupid>net.sf.dozer</groupid> <artifactid>dozer</artifactid> <version>5.5.1</version> </dependency>
测试用例:
@data public class person { private string name; private integer age; private address address; @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); } } @data public class address { private string type; private string value; }
输出:
p1=person(name=peter, age=31, address=address(type=home, value=北京)) p2=person(name=peter, age=31, address=address(type=office, value=北京))
注意:在万次测试中dozer有一个很严重的问题,如果dozerbeanmapper对象在for循环中创建,效率(dozer:7358)降低近10倍。由于dozerbeanmapper是线程安全的,所以不应该每次都创建新的实例。可以自带的单例工厂dozerbeanmappersingletonwrapper来创建mapper,或集成到spring中。
还有更暴力的,创建一个people类:
@data public class people { private string name; private string age;//这里已经不是integer了 private address address; @test public void testdozer() { person p1=personfactory.newprototypeinstance(); mapper mapper = new dozerbeanmapper(); people p2 = mapper.map(p1, people.class); p2.getaddress().settype("office"); system.out.println("p1=" + p1); system.out.println("p2=" + p2); } }
只要属性名相同,干~
继续蹂躏:
@data public class people { private string name; private string age; private map<string,string> address;//�� @test public void testdozer() { person p1=personfactory.newprototypeinstance(); mapper mapper = new dozerbeanmapper(); people p2 = mapper.map(p1, people.class); p2.getaddress().put("type", "office"); system.out.println("p1=" + p1); system.out.println("p2=" + p2); } }
利用commons-beanutils复制对象
maven依赖
<dependency> <groupid>commons-beanutils</groupid> <artifactid>commons-beanutils</artifactid> <version>1.9.3</version> </dependency>
测试用例:
@data public class person { private string name; private string age; private address address; @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复制对象
maven依赖:
<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); }
结果大跌眼镜,cglib这么牛x,居然是浅拷贝。不过cglib提供了扩展能力:
@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复制对象
orika的作用不仅仅在于处理bean拷贝,更擅长各种类型之间的转换。
maven依赖:
<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复制对象
给spring个面子,貌似它不支持深拷贝。
person p1=personfactory.newprototypeinstance(); person p2 = new person(); person p2 = (person) beanutils.clonebean(p1); //beanutils.copyproperties(p2, p1);//这个更没戏
深拷贝性能对比
@test public void testbatchdozer(){ long start=system.currenttimemillis(); mapper mapper = new dozerbeanmapper(); for(int i=0;i<10000;i++){ person p1=personfactory.newprototypeinstance(); person p2 = mapper.map(p1, person.class); } system.out.println("dozer:"+(system.currenttimemillis()-start)); //dozer:721 } @test public void testbatchbeanutils(){ long start=system.currenttimemillis(); for(int i=0;i<10000;i++){ person p1=personfactory.newprototypeinstance(); try { person p2=(person) beanutils.clonebean(p1); } catch (exception e) { e.printstacktrace(); } } system.out.println("commons-beanutils:"+(system.currenttimemillis()-start)); //commons-beanutils:229 } @test public void testbatchcglib(){ long start=system.currenttimemillis(); for(int i=0;i<10000;i++){ 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; } }); } system.out.println("cglib:"+(system.currenttimemillis()-start)); //cglib:133 } @test public void testbatchserial(){ long start=system.currenttimemillis(); for(int i=0;i<10000;i++){ person p1=personfactory.newprototypeinstance(); person p2=p1.deepclone(); } system.out.println("serializable:"+(system.currenttimemillis()-start)); //serializable:687 } @test public void testbatchorika() { mapperfactory mapperfactory = new defaultmapperfactory.builder().build(); mapperfactory.classmap(person.class, person.class) .field("name", "name") .bydefault() .register(); converterfactory converterfactory = mapperfactory.getconverterfactory(); mapperfacade mapper = mapperfactory.getmapperfacade(); long start=system.currenttimemillis(); for(int i=0;i<10000;i++){ person p1=personfactory.newprototypeinstance(); person p2 = mapper.map(p1, person.class); } system.out.println("orika:"+(system.currenttimemillis()-start)); //orika:83 } @test public void testbatchclone(){ long start=system.currenttimemillis(); for(int i=0;i<10000;i++){ person p1=personfactory.newprototypeinstance(); try { person p2=(person) p1.clone(); } catch (clonenotsupportedexception e) { e.printstacktrace(); } } system.out.println("clone:"+(system.currenttimemillis()-start)); //clone:8 }
(10k)性能比较:
//dozer:721 //commons-beanutils:229 //cglib:133 //serializable:687 //orika:83 //clone:8
深拷贝总结
原生的clone效率无疑是最高的,用脚趾头都能想到。
偶尔用一次,用哪个都问题都不大。
一般性能要求稍高的应用场景,cglib和orika完全可以接受。
另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
上一篇: 回调函数的意义以及python实现实例
下一篇: Python 含参构造函数实例详解