前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java对象拷贝最完全解说 转

java对象拷贝最完全解说 转

作者头像
stys35
发布2019-03-05 16:29:24
1.2K0
发布2019-03-05 16:29:24
举报
文章被收录于专栏:工作笔记精华工作笔记精华

java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的:

@Test public void testassign(){   Person p1=new Person();   p1.setAge(31);   p1.setName("Peter");

  Person p2=p1;   System.out.println(p1==p2);//true } 1 2 3 4 5 6 7 8 9 如果创建一个对象的新的副本,也就是说他们的初始状态完全一样,但以后可以改变各自的状态,而互不影响,就需要用到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] } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 该测试用例只有两个基本类型的成员,测试达到目的了。

事情貌似没有这么简单,为Person增加一个Address类的成员:

@Data public class Address {     private String type;     private String value; } 1 2 3 4 5 再来测试,问题来了。

@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); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 查看输出:

false p1=Person(name=Peter, age=31, address=Address(type=Office, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京)) 1 2 3 遇到了点麻烦,只修改了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();     } } 1 2 3 4 5 6 7 8 9 10 这样还不够,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;     } } 1 2 3 4 5 6 7 8 9 10 11 12 13 重新跑前面的测试用例:

false p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京)) 1 2 3 clone方式深拷贝小结 ① 如果有一个非原生成员,如自定义对象的成员,那么就需要:

该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为public可见。 同时,修改被复制类的clone()方法,增加成员的克隆逻辑。 ② 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法。

与对象成员不同,继承关系中的clone不需要被复制类的clone()做多余的工作。

一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。

前面的实例还可以接受,如果有N个对象成员,有M层继承关系,就会很麻烦。

利用序列化实现深拷贝 clone机制不是强类型的限制,比如实现了Cloneable并没有强制继承链上的对象也实现;也没有强制要求覆盖clone()方法。因此编码过程中比较容易忽略其中一个环节,对于复杂的项目排查就是困难了。

要寻找可靠的,简单的方法,序列化就是一种途径。

被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。

实现自己的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;     } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 原型工厂类 为了便于测试,也节省篇幅,封装一个工厂类。

公平起见,避免某些工具库使用缓存机制,使用原型方式工厂。

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;     } } 1 2 3 4 5 6 7 8 9 10 11 12 13 利用Dozer拷贝对象 Dozer是一个Bean处理类库。

maven依赖

<dependency>   <groupId>net.sf.dozer</groupId>   <artifactId>dozer</artifactId>   <version>5.5.1</version> </dependency> 1 2 3 4 5 测试用例:

@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; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 输出:

p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京)) 1 2 注意:在万次测试中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);     } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 只要属性名相同,干~

继续蹂躏:

@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);     } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 利用Commons-BeanUtils复制对象 maven依赖

<dependency>   <groupId>commons-beanutils</groupId>   <artifactId>commons-beanutils</artifactId>   <version>1.9.3</version> </dependency> 1 2 3 4 5 测试用例:

@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();         }     } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 利用cglib复制对象 maven依赖:

<dependency>   <groupId>cglib</groupId>   <artifactId>cglib</artifactId>   <version>3.2.4</version> </dependency> 1 2 3 4 5 测试用例:

@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); } 1 2 3 4 5 6 7 8 9 10 结果大跌眼镜,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); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Orika复制对象 orika的作用不仅仅在于处理bean拷贝,更擅长各种类型之间的转换。

maven依赖:

<dependency>   <groupId>ma.glasnost.orika</groupId>   <artifactId>orika-core</artifactId>   <version>1.5.0</version> </dependency> </dependencies> 1 2 3 4 5 6 测试用例:

@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); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Spring BeanUtils复制对象 给Spring个面子,貌似它不支持深拷贝。

Person p1=PersonFactory.newPrototypeInstance(); Person p2 = new Person(); Person p2 = (Person) BeanUtils.cloneBean(p1); //BeanUtils.copyProperties(p2, p1);//这个更没戏 1 2 3 4 深拷贝性能对比 @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 } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 (10k)性能比较:

//dozer:721 //commons-beanutils:229 //cglib:133 //serializable:687 //orika:83 //clone:8 1 2 3 4 5 6 深拷贝总结 原生的clone效率无疑是最高的,用脚趾头都能想到。

偶尔用一次,用哪个都问题都不大。

一般性能要求稍高的应用场景,cglib和orika完全可以接受。

另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖 ---------------------  作者:54powerman  来源:CSDN  原文:https://blog.csdn.net/54powerman/article/details/64920431  版权声明:本文为博主原创文章,转载请附上博文链接!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档