java對(duì)象拷貝詳解及實(shí)例
java對(duì)象拷貝詳解及實(shí)例
Java賦值是復(fù)制對(duì)象引用,如果我們想要得到一個(gè)對(duì)象的副本,使用賦值操作是無法達(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ì)象的新的副本,也就是說他們的初始狀態(tài)完全一樣,但以后可以改變各自的狀態(tài),而互不影響,就需要用到j(luò)ava中對(duì)象的復(fù)制,如原生的clone()方法。
如何進(jìn)行對(duì)象克隆
Object對(duì)象有個(gè)clone()方法,實(shí)現(xiàn)了對(duì)象中各個(gè)屬性的復(fù)制,但它的可見范圍是protected的,所以實(shí)體類使用克隆的前提是:
① 實(shí)現(xiàn)Cloneable接口,這是一個(gè)標(biāo)記接口,自身沒有方法。
② 覆蓋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]
}
該測(cè)試用例只有兩個(gè)基本類型的成員,測(cè)試達(dá)到目的了。
事情貌似沒有這么簡(jiǎn)單,為Person增加一個(gè)Address類的成員:
@Data
public class Address {
private String type;
private String value;
}
再來測(cè)試,問題來了。
@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的地址類型,兩個(gè)地址類型都變成了Office。
淺拷貝和深拷貝
前面實(shí)例中是淺拷貝和深拷貝的典型用例。
淺拷貝:被復(fù)制對(duì)象的所有值屬性都含有與原來對(duì)象的相同,而所有的對(duì)象引用屬性仍然指向原來的對(duì)象。
深拷貝:在淺拷貝的基礎(chǔ)上,所有引用其他對(duì)象的變量也進(jìn)行了clone,并指向被復(fù)制過的新對(duì)象。
也就是說,一個(gè)默認(rèn)的clone()方法實(shí)現(xiàn)機(jī)制,仍然是賦值。
如果一個(gè)被復(fù)制的屬性都是基本類型,那么只需要實(shí)現(xiàn)當(dāng)前類的cloneable機(jī)制就可以了,此為淺拷貝。
如果被復(fù)制對(duì)象的屬性包含其他實(shí)體類對(duì)象引用,那么這些實(shí)體類對(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ì)象的成員,那么就需要:
- 該成員實(shí)現(xiàn)Cloneable接口并覆蓋clone()方法,不要忘記提升為public可見。
- 同時(shí),修改被復(fù)制類的clone()方法,增加成員的克隆邏輯。
② 如果被復(fù)制對(duì)象不是直接繼承Object,中間還有其它繼承層次,每一層super類都需要實(shí)現(xiàn)Cloneable接口并覆蓋clone()方法。
與對(duì)象成員不同,繼承關(guān)系中的clone不需要被復(fù)制類的clone()做多余的工作。
一句話來說,如果實(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)類型的限制,比如實(shí)現(xiàn)了Cloneable并沒有強(qiáng)制繼承鏈上的對(duì)象也實(shí)現(xiàn);也沒有強(qiáng)制要求覆蓋clone()方法。因此編碼過程中比較容易忽略其中一個(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ì)深拷貝來說沒毛病。
2.實(shí)現(xiàn)自己的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;
}
}
原型工廠類
為了便于測(cè)試,也節(jié)省篇幅,封裝一個(gè)工廠類。
公平起見,避免某些工具庫使用緩存機(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處理類庫。
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=北京))
注意:在萬次測(cè)試中dozer有一個(gè)很嚴(yán)重的問題,如果DozerBeanMapper對(duì)象在for循環(huán)中創(chuàng)建,效率(dozer:7358)降低近10倍。由于DozerBeanMapper是線程安全的,所以不應(yīng)該每次都創(chuàng)建新的實(shí)例。可以自帶的單例工廠DozerBeanMapperSingletonWrapper來創(chuàng)建mapper,或集成到spring中。
還有更暴力的,創(chuàng)建一個(gè)People類:
@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,居然是淺拷貝。不過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)各種類型之間的轉(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è)更沒戲
深拷貝性能對(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效率無疑是最高的,用腳趾頭都能想到。
偶爾用一次,用哪個(gè)都問題都不大。
一般性能要求稍高的應(yīng)用場(chǎng)景,cglib和orika完全可以接受。
另外一個(gè)考慮的因素,如果項(xiàng)目已經(jīng)引入了某個(gè)依賴,就用那個(gè)依賴來做吧,沒必要再引入一個(gè)第三方依賴。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Java利用Swagger2自動(dòng)生成對(duì)外接口的文檔
這篇文章主要介紹了Java利用Swagger2自動(dòng)生成對(duì)外接口的文檔,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06
使用Post方式提交數(shù)據(jù)到Tomcat服務(wù)器的方法
這篇將介紹使用Post方式提交數(shù)據(jù)到服務(wù)器,由于Post的方式和Get方式創(chuàng)建Web工程是一模一樣的,只用幾個(gè)地方的代碼不同,這篇文章主要介紹了使用Post方式提交數(shù)據(jù)到Tomcat服務(wù)器的方法,感興趣的朋友一起學(xué)習(xí)吧2016-04-04
springboot中的controller參數(shù)映射問題小結(jié)
這篇文章主要介紹了springboot中的controller參數(shù)映射問題小結(jié),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-12-12
Spring Boot項(xiàng)目添加外部Jar包以及配置多數(shù)據(jù)源的完整步驟
這篇文章主要給大家介紹了關(guān)于Spring Boot項(xiàng)目添加外部Jar包以及配置多數(shù)據(jù)源的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Java農(nóng)夫過河問題的繼承與多態(tài)實(shí)現(xiàn)詳解
這篇文章主要介紹了Java農(nóng)夫過河問題的繼承與多態(tài)實(shí)現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Java String字符串和Unicode字符相互轉(zhuǎn)換代碼詳解
這篇文章主要介紹了Java String字符串和Unicode字符相互轉(zhuǎn)換代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05

