這篇文章主要介紹了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完全可以接受。
另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。
以上是實例詳解java物件拷貝的詳細內容。更多資訊請關注PHP中文網其他相關文章!