国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

java對象拷貝詳解及實例

2019-11-26 12:32:27
字體:
來源:轉載
供稿:網友

java對象拷貝詳解及實例

Java賦值是復制對象引用,如果我們想要得到一個對象的副本,使用賦值操作是無法達到目的的:

@Testpublic 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。

@Datapublic class Person implements Cloneable {  private String name;  private Integer age;  private Address address;  @Override  protected Object clone() throws CloneNotSupportedException {    return super.clone();  }}@Testpublic 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類的成員:

@Datapublic class Address {  private String type;  private String value;}

再來測試,問題來了。

@Testpublic 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);}

查看輸出:

falsep1=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()方法。

@Datapublic class Address implements Cloneable {  private String type;  private String value;  @Override  protected Object clone() throws CloneNotSupportedException {    return super.clone();  }}

這樣還不夠,Person的clone()需要顯式地clone其引用成員。

@Datapublic 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;  }}

重新跑前面的測試用例:

falsep1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

clone方式深拷貝小結

① 如果有一個非原生成員,如自定義對象的成員,那么就需要:

  1. 該成員實現Cloneable接口并覆蓋clone()方法,不要忘記提升為public可見。
  2. 同時,修改被復制類的clone()方法,增加成員的克隆邏輯。

② 如果被復制對象不是直接繼承Object,中間還有其它繼承層次,每一層super類都需要實現Cloneable接口并覆蓋clone()方法。

與對象成員不同,繼承關系中的clone不需要被復制類的clone()做多余的工作。

一句話來說,如果實現完整的深拷貝,需要被復制對象的繼承鏈、引用鏈上的每一個對象都實現克隆機制。

前面的實例還可以接受,如果有N個對象成員,有M層繼承關系,就會很麻煩。

利用序列化實現深拷貝

clone機制不是強類型的限制,比如實現了Cloneable并沒有強制繼承鏈上的對象也實現;也沒有強制要求覆蓋clone()方法。因此編碼過程中比較容易忽略其中一個環節,對于復雜的項目排查就是困難了。

要尋找可靠的,簡單的方法,序列化就是一種途徑。

1.被復制對象的繼承鏈、引用鏈上的每一個對象都實現java.io.Serializable接口。這個比較簡單,不需要實現任何方法,serialVersionID的要求不強制,對深拷貝來說沒毛病。

2.實現自己的deepClone方法,將this寫入流,再讀出來。俗稱:冷凍-解凍。

@Datapublic 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>

測試用例:

@Datapublic 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);  }}@Datapublic 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類:

@Datapublic 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);  }}

只要屬性名相同,干~

繼續蹂躪:

@Datapublic 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>

測試用例:

@Datapublic 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>

測試用例:

@Testpublic 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提供了擴展能力:

@Testpublic 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>

測試用例:

@Testpublic 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);//這個更沒戲

深拷貝性能對比

@Testpublic 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}@Testpublic 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}@Testpublic 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}@Testpublic 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}@Testpublic 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}@Testpublic 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完全可以接受。

另外一個考慮的因素,如果項目已經引入了某個依賴,就用那個依賴來做吧,沒必要再引入一個第三方依賴。

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 博湖县| 连南| 西和县| 花垣县| 兴文县| 盐城市| 开封县| 蛟河市| 浦东新区| 高清| 永仁县| 承德县| 池州市| 长治县| 锡林郭勒盟| 满城县| 潞城市| 昭平县| 台山市| 五莲县| 三门县| 大港区| 灵丘县| 盐边县| 保山市| 武宁县| 古交市| 南康市| 芒康县| 仙游县| 遂溪县| 德庆县| 濮阳市| 邻水| 汤阴县| 凉城县| 通海县| 酒泉市| 仙桃市| 伊宁市| 吴旗县|