本篇文章為大家展示了一文帶你讀懂java中的對(duì)象拷貝,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶,將通過(guò)不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:主機(jī)域名、雅安服務(wù)器托管、營(yíng)銷(xiāo)軟件、網(wǎng)站建設(shè)、天涯網(wǎng)站維護(hù)、網(wǎng)站推廣。
java對(duì)象拷貝詳解及實(shí)例
Java賦值是復(fù)制對(duì)象引用,如果我們想要得到一個(gè)對(duì)象的副本,使用賦值操作是無(wú)法達(dá)到目的的:
@Test public void testassign(){ Person p1=new Person(); p1.setAge(31); p1.setName("Peter"); Person p2=p1; System.out.println(p1==p2);//true }
如果創(chuàng)建一個(gè)對(duì)象的新的副本,也就是說(shuō)他們的初始狀態(tài)完全一樣,但以后可以改變各自的狀態(tài),而互不影響,就需要用到j(luò)ava中對(duì)象的復(fù)制,如原生的clone()方法。
如何進(jìn)行對(duì)象克隆
Object對(duì)象有個(gè)clone()方法,實(shí)現(xiàn)了對(duì)象中各個(gè)屬性的復(fù)制,但它的可見(jiàn)范圍是protected的,所以實(shí)體類(lèi)使用克隆的前提是:
① 實(shí)現(xiàn)Cloneable接口,這是一個(gè)標(biāo)記接口,自身沒(méi)有方法。
② 覆蓋clone()方法,可見(jiàn)性提升為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] }
該測(cè)試用例只有兩個(gè)基本類(lèi)型的成員,測(cè)試達(dá)到目的了。
事情貌似沒(méi)有這么簡(jiǎn)單,為Person增加一個(gè)Address類(lèi)的成員:
@Data public class Address { private String type; private String value; }
再來(lái)測(cè)試,問(wèn)題來(lái)了。
@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=北京))
遇到了點(diǎn)麻煩,只修改了p2的地址類(lèi)型,兩個(gè)地址類(lèi)型都變成了Office。
淺拷貝和深拷貝
前面實(shí)例中是淺拷貝和深拷貝的典型用例。
淺拷貝:被復(fù)制對(duì)象的所有值屬性都含有與原來(lái)對(duì)象的相同,而所有的對(duì)象引用屬性仍然指向原來(lái)的對(duì)象。
深拷貝:在淺拷貝的基礎(chǔ)上,所有引用其他對(duì)象的變量也進(jìn)行了clone,并指向被復(fù)制過(guò)的新對(duì)象。
也就是說(shuō),一個(gè)默認(rèn)的clone()方法實(shí)現(xiàn)機(jī)制,仍然是賦值。
如果一個(gè)被復(fù)制的屬性都是基本類(lèi)型,那么只需要實(shí)現(xiàn)當(dāng)前類(lèi)的cloneable機(jī)制就可以了,此為淺拷貝。
如果被復(fù)制對(duì)象的屬性包含其他實(shí)體類(lèi)對(duì)象引用,那么這些實(shí)體類(lèi)對(duì)象都需要實(shí)現(xiàn)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; } }
重新跑前面的測(cè)試用例:
false p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
clone方式深拷貝小結(jié)
① 如果有一個(gè)非原生成員,如自定義對(duì)象的成員,那么就需要:
② 如果被復(fù)制對(duì)象不是直接繼承Object,中間還有其它繼承層次,每一層super類(lèi)都需要實(shí)現(xiàn)Cloneable接口并覆蓋clone()方法。
與對(duì)象成員不同,繼承關(guān)系中的clone不需要被復(fù)制類(lèi)的clone()做多余的工作。
一句話來(lái)說(shuō),如果實(shí)現(xiàn)完整的深拷貝,需要被復(fù)制對(duì)象的繼承鏈、引用鏈上的每一個(gè)對(duì)象都實(shí)現(xiàn)克隆機(jī)制。
前面的實(shí)例還可以接受,如果有N個(gè)對(duì)象成員,有M層繼承關(guān)系,就會(huì)很麻煩。
利用序列化實(shí)現(xiàn)深拷貝
clone機(jī)制不是強(qiáng)類(lèi)型的限制,比如實(shí)現(xiàn)了Cloneable并沒(méi)有強(qiáng)制繼承鏈上的對(duì)象也實(shí)現(xiàn);也沒(méi)有強(qiáng)制要求覆蓋clone()方法。因此編碼過(guò)程中比較容易忽略其中一個(gè)環(huán)節(jié),對(duì)于復(fù)雜的項(xiàng)目排查就是困難了。
要尋找可靠的,簡(jiǎn)單的方法,序列化就是一種途徑。
1.被復(fù)制對(duì)象的繼承鏈、引用鏈上的每一個(gè)對(duì)象都實(shí)現(xiàn)java.io.Serializable接口。這個(gè)比較簡(jiǎn)單,不需要實(shí)現(xiàn)任何方法,serialVersionID的要求不強(qiáng)制,對(duì)深拷貝來(lái)說(shuō)沒(méi)毛病。
2.實(shí)現(xiàn)自己的deepClone方法,將this寫(xiě)入流,再讀出來(lái)。俗稱:冷凍-解凍。
@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; } }
原型工廠類(lèi)
為了便于測(cè)試,也節(jié)省篇幅,封裝一個(gè)工廠類(lèi)。
公平起見(jiàn),避免某些工具庫(kù)使用緩存機(jī)制,使用原型方式工廠。
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拷貝對(duì)象
Dozer是一個(gè)Bean處理類(lèi)庫(kù)。
maven依賴
<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.5.1</version> </dependency>
測(cè)試用例:
@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=北京))
注意:在萬(wàn)次測(cè)試中dozer有一個(gè)很?chē)?yán)重的問(wèn)題,如果DozerBeanMapper對(duì)象在for循環(huán)中創(chuàng)建,效率(dozer:7358)降低近10倍。由于DozerBeanMapper是線程安全的,所以不應(yīng)該每次都創(chuàng)建新的實(shí)例??梢宰詭У膯卫SDozerBeanMapperSingletonWrapper來(lái)創(chuàng)建mapper,或集成到spring中。
還有更暴力的,創(chuàng)建一個(gè)People類(lèi):
@Data public class People { private String name; private String age;//這里已經(jīng)不是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); } }
只要屬性名相同,干~
繼續(xù)蹂躪:
@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復(fù)制對(duì)象
maven依賴
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency>
測(cè)試用例:
@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復(fù)制對(duì)象
maven依賴:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency>
測(cè)試用例:
@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); }
結(jié)果大跌眼鏡,cglib這么牛x,居然是淺拷貝。不過(guò)cglib提供了擴(kuò)展能力:
@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復(fù)制對(duì)象
orika的作用不僅僅在于處理bean拷貝,更擅長(zhǎng)各種類(lèi)型之間的轉(zhuǎn)換。
maven依賴:
<dependency> <groupId>ma.glasnost.orika</groupId> <artifactId>orika-core</artifactId> <version>1.5.0</version> </dependency> </dependencies>
測(cè)試用例:
@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復(fù)制對(duì)象
給Spring個(gè)面子,貌似它不支持深拷貝。
Person p1=PersonFactory.newPrototypeInstance(); Person p2 = new Person(); Person p2 = (Person) BeanUtils.cloneBean(p1); //BeanUtils.copyProperties(p2, p1);//這個(gè)更沒(méi)戲
深拷貝性能對(duì)比
@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
深拷貝總結(jié)
原生的clone效率無(wú)疑是最高的,用腳趾頭都能想到。
偶爾用一次,用哪個(gè)都問(wèn)題都不大。
一般性能要求稍高的應(yīng)用場(chǎng)景,cglib和orika完全可以接受。
另外一個(gè)考慮的因素,如果項(xiàng)目已經(jīng)引入了某個(gè)依賴,就用那個(gè)依賴來(lái)做吧,沒(méi)必要再引入一個(gè)第三方依賴。
上述內(nèi)容就是一文帶你讀懂java中的對(duì)象拷貝,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
本文名稱:一文帶你讀懂java中的對(duì)象拷貝
文章鏈接:http://bm7419.com/article34/pcdspe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、服務(wù)器托管、云服務(wù)器、定制網(wǎng)站、網(wǎng)站維護(hù)、網(wǎng)站內(nèi)鏈
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)