java对象clone()方法

我只是一个虾纸丫 提交于 2019-12-02 08:48:36

java对象clone()方法

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

  1.  
    @Test
  2.  
    public void testassign(){
  3.  
    Person p1=new Person();
  4.  
    p1.setAge(31);
  5.  
    p1.setName("Peter");
  6.  
     
  7.  
    Person p2=p1;
  8.  
    System.out.println(p1==p2);//true
  9.  
    }

如果创建一个对象的新的副本,也就是说他们的初始状态完全一样,但以后可以改变各自的状态,而互不影响,就需要用到java中对象的复制,如原生的clone()方法。

如何进行对象克隆

Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的,所以实体类使用克隆的前提是:

① 实现Cloneable接口,这是一个标记接口,自身没有方法。 
② 覆盖clone()方法,可见性提升为public。

  1.  
    @Data
  2.  
    public class Person implements Cloneable {
  3.  
    private String name;
  4.  
    private Integer age;
  5.  
    private Address address;
  6.  
    @Override
  7.  
    protected Object clone() throws CloneNotSupportedException {
  8.  
    return super.clone();
  9.  
    }
  10.  
    }
  11.  
     
  12.  
    @Test
  13.  
    public void testShallowCopy() throws Exception{
  14.  
    Person p1=new Person();
  15.  
    p1.setAge(31);
  16.  
    p1.setName("Peter");
  17.  
     
  18.  
    Person p2=(Person) p1.clone();
  19.  
    System.out.println(p1==p2);//false
  20.  
    p2.setName("Jacky");
  21.  
    System.out.println("p1="+p1);//p1=Person [name=Peter, age=31]
  22.  
    System.out.println("p2="+p2);//p2=Person [name=Jacky, age=31]
  23.  
    }

该测试用例只有两个基本类型的成员,测试达到目的了。

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

  1.  
    @Data
  2.  
    public class Address {
  3.  
    private String type;
  4.  
    private String value;
  5.  
    }

再来测试,问题来了。

  1.  
    @Test
  2.  
    public void testShallowCopy() throws Exception{
  3.  
    Address address=new Address();
  4.  
    address.setType("Home");
  5.  
    address.setValue("北京");
  6.  
     
  7.  
    Person p1=new Person();
  8.  
    p1.setAge(31);
  9.  
    p1.setName("Peter");
  10.  
    p1.setAddress(address);
  11.  
     
  12.  
    Person p2=(Person) p1.clone();
  13.  
    System.out.println(p1==p2);//false
  14.  
     
  15.  
    p2.getAddress().setType("Office");
  16.  
    System.out.println("p1="+p1);
  17.  
    System.out.println("p2="+p2);
  18.  
    }

查看输出:

  1.  
    false
  2.  
    p1=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
  3.  
    p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

遇到了点麻烦,只修改了p2的地址类型,两个地址类型都变成了Office。

浅拷贝和深拷贝

前面实例中是浅拷贝和深拷贝的典型用例。

浅拷贝:被复制对象的所有值属性都含有与原来对象的相同,而所有的对象引用属性仍然指向原来的对象。

深拷贝:在浅拷贝的基础上,所有引用其他对象的变量也进行了clone,并指向被复制过的新对象。

也就是说,一个默认的clone()方法实现机制,仍然是赋值。

如果一个被复制的属性都是基本类型,那么只需要实现当前类的cloneable机制就可以了,此为浅拷贝。

如果被复制对象的属性包含其他实体类对象引用,那么这些实体类对象都需要实现cloneable接口并覆盖clone()方法。

  1.  
    @Data
  2.  
    public class Address implements Cloneable {
  3.  
    private String type;
  4.  
    private String value;
  5.  
     
  6.  
    @Override
  7.  
    protected Object clone() throws CloneNotSupportedException {
  8.  
    return super.clone();
  9.  
    }
  10.  
    }

这样还不够,Person的clone()需要显式地clone其引用成员。

  1.  
    @Data
  2.  
    public class Person implements Cloneable {
  3.  
    private String name;
  4.  
    private Integer age;
  5.  
    private Address address;
  6.  
    @Override
  7.  
    protected Object clone() throws CloneNotSupportedException {
  8.  
    Object obj=super.clone();
  9.  
    Address a=((Person)obj).getAddress();
  10.  
    ((Person)obj).setAddress((Address) a.clone());
  11.  
    return obj;
  12.  
    }
  13.  
    }

重新跑前面的测试用例:

  1.  
    false
  2.  
    p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
  3.  
    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()方法。因此编码过程中比较容易忽略其中一个环节,对于复杂的项目排查就是困难了。

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

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

  • 实现自己的deepClone方法,将this写入流,再读出来。俗称:冷冻-解冻。

  1.  
    @Data
  2.  
    public class Person implements Serializable {
  3.  
    private String name;
  4.  
    private Integer age;
  5.  
    private Address address;
  6.  
    public Person deepClone() {
  7.  
    Person p2=null;
  8.  
    Person p1=this;
  9.  
    PipedOutputStream out=new PipedOutputStream();
  10.  
    PipedInputStream in=new PipedInputStream();
  11.  
    try {
  12.  
    in.connect(out);
  13.  
    } catch (IOException e) {
  14.  
    e.printStackTrace();
  15.  
    }
  16.  
     
  17.  
    try(ObjectOutputStream bo=new ObjectOutputStream(out);
  18.  
    ObjectInputStream bi=new ObjectInputStream(in);) {
  19.  
    bo.writeObject(p1);
  20.  
    p2=(Person) bi.readObject();
  21.  
     
  22.  
    } catch (Exception e) {
  23.  
    e.printStackTrace();
  24.  
    }
  25.  
    return p2;
  26.  
    }
  27.  
    }

原型工厂类

为了便于测试,也节省篇幅,封装一个工厂类。

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

  1.  
    public class PersonFactory{
  2.  
    public static Person newPrototypeInstance(){
  3.  
    Address address = new Address();
  4.  
    address.setType("Home");
  5.  
    address.setValue("北京");
  6.  
     
  7.  
    Person p1 = new Person();
  8.  
    p1.setAddress(address);
  9.  
    p1.setAge(31);
  10.  
    p1.setName("Peter");
  11.  
    return p1;
  12.  
    }
  13.  
    }

利用Dozer拷贝对象

Dozer是一个Bean处理类库。

maven依赖

  1.  
    <dependency>
  2.  
    <groupId>net.sf.dozer</groupId>
  3.  
    <artifactId>dozer</artifactId>
  4.  
    <version>5.5.1</version>
  5.  
    </dependency>

测试用例:

  1.  
    @Data
  2.  
    public class Person {
  3.  
    private String name;
  4.  
    private Integer age;
  5.  
    private Address address;
  6.  
     
  7.  
    @Test
  8.  
    public void testDozer() {
  9.  
    Person p1=PersonFactory.newPrototypeInstance();
  10.  
    Mapper mapper = new DozerBeanMapper();
  11.  
    Person p2 = mapper.map(p1, Person.class);
  12.  
    p2.getAddress().setType("Office");
  13.  
    System.out.println("p1=" + p1);
  14.  
    System.out.println("p2=" + p2);
  15.  
    }
  16.  
    }
  17.  
     
  18.  
    @Data
  19.  
    public class Address {
  20.  
    private String type;
  21.  
    private String value;
  22.  
    }

输出:

  1.  
    p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
  2.  
    p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

注意:在万次测试中dozer有一个很严重的问题,如果DozerBeanMapper对象在for循环中创建,效率(dozer:7358)降低近10倍。由于DozerBeanMapper是线程安全的,所以不应该每次都创建新的实例。可以自带的单例工厂DozerBeanMapperSingletonWrapper来创建mapper,或集成到spring中。

还有更暴力的,创建一个People类:

  1.  
    @Data
  2.  
    public class People {
  3.  
    private String name;
  4.  
    private String age;//这里已经不是Integer了
  5.  
    private Address address;
  6.  
     
  7.  
    @Test
  8.  
    public void testDozer() {
  9.  
    Person p1=PersonFactory.newPrototypeInstance();
  10.  
    Mapper mapper = new DozerBeanMapper();
  11.  
    People p2 = mapper.map(p1, People.class);
  12.  
    p2.getAddress().setType("Office");
  13.  
    System.out.println("p1=" + p1);
  14.  
    System.out.println("p2=" + p2);
  15.  
    }
  16.  
    }

只要属性名相同,干~

继续蹂躏:

  1.  
    @Data
  2.  
    public class People {
  3.  
    private String name;
  4.  
    private String age;
  5.  
    private Map<String,String> address;//��
  6.  
     
  7.  
    @Test
  8.  
    public void testDozer() {
  9.  
    Person p1=PersonFactory.newPrototypeInstance();
  10.  
    Mapper mapper = new DozerBeanMapper();
  11.  
    People p2 = mapper.map(p1, People.class);
  12.  
    p2.getAddress().put("type", "Office");
  13.  
    System.out.println("p1=" + p1);
  14.  
    System.out.println("p2=" + p2);
  15.  
    }
  16.  
    }

利用Commons-BeanUtils复制对象

maven依赖

  1.  
    <dependency>
  2.  
    <groupId>commons-beanutils</groupId>
  3.  
    <artifactId>commons-beanutils</artifactId>
  4.  
    <version>1.9.3</version>
  5.  
    </dependency>

测试用例:

  1.  
    @Data
  2.  
    public class Person {
  3.  
    private String name;
  4.  
    private String age;
  5.  
    private Address address;
  6.  
     
  7.  
    @Test
  8.  
    public void testCommonsBeanUtils(){
  9.  
    Person p1=PersonFactory.newPrototypeInstance();
  10.  
    try {
  11.  
    Person p2=(Person) BeanUtils.cloneBean(p1);
  12.  
    System.out.println("p1=" + p1);
  13.  
    p2.getAddress().setType("Office");
  14.  
    System.out.println("p2=" + p2);
  15.  
    } catch (Exception e) {
  16.  
    e.printStackTrace();
  17.  
    }
  18.  
    }
  19.  
    }

利用cglib复制对象

maven依赖:

  1.  
    <dependency>
  2.  
    <groupId>cglib</groupId>
  3.  
    <artifactId>cglib</artifactId>
  4.  
    <version>3.2.4</version>
  5.  
    </dependency>

测试用例:

  1.  
    @Test
  2.  
    public void testCglib(){
  3.  
    Person p1=PersonFactory.newPrototypeInstance();
  4.  
    BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false);
  5.  
    Person p2=new Person();
  6.  
    beanCopier.copy(p1, p2,null);
  7.  
    p2.getAddress().setType("Office");
  8.  
    System.out.println("p1=" + p1);
  9.  
    System.out.println("p2=" + p2);
  10.  
    }

结果大跌眼镜,cglib这么牛x,居然是浅拷贝。不过cglib提供了扩展能力:

  1.  
    @Test
  2.  
    public void testCglib(){
  3.  
    Person p1=PersonFactory.newPrototypeInstance();
  4.  
    BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
  5.  
    Person p2=new Person();
  6.  
    beanCopier.copy(p1, p2, new Converter(){
  7.  
    @Override
  8.  
    public Object convert(Object value, Class target, Object context) {
  9.  
    if(target.isSynthetic()){
  10.  
    BeanCopier.create(target, target, true).copy(value, value, this);
  11.  
    }
  12.  
    return value;
  13.  
    }
  14.  
    });
  15.  
    p2.getAddress().setType("Office");
  16.  
    System.out.println("p1=" + p1);
  17.  
    System.out.println("p2=" + p2);
  18.  
    }

Orika复制对象

orika的作用不仅仅在于处理bean拷贝,更擅长各种类型之间的转换。

maven依赖:

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

测试用例:

  1.  
    @Test
  2.  
    public void testOrika() {
  3.  
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
  4.  
     
  5.  
    mapperFactory.classMap(Person.class, Person.class)
  6.  
    .byDefault()
  7.  
    .register();
  8.  
    ConverterFactory converterFactory = mapperFactory.getConverterFactory();
  9.  
    MapperFacade mapper = mapperFactory.getMapperFacade();
  10.  
     
  11.  
    Person p1=PersonFactory.newPrototypeInstance();
  12.  
    Person p2 = mapper.map(p1, Person.class);
  13.  
    System.out.println("p1=" + p1);
  14.  
    p2.getAddress().setType("Office");
  15.  
    System.out.println("p2=" + p2);
  16.  
    }

Spring BeanUtils复制对象

给Spring个面子,貌似它不支持深拷贝。

  1.  
    Person p1=PersonFactory.newPrototypeInstance();
  2.  
    Person p2 = new Person();
  3.  
    Person p2 = (Person) BeanUtils.cloneBean(p1);
  4.  
    //BeanUtils.copyProperties(p2, p1);//这个更没戏

深拷贝性能对比

  1.  
    @Test
  2.  
    public void testBatchDozer(){
  3.  
    Long start=System.currentTimeMillis();
  4.  
    Mapper mapper = new DozerBeanMapper();
  5.  
    for(int i=0;i<10000;i++){
  6.  
    Person p1=PersonFactory.newPrototypeInstance();
  7.  
    Person p2 = mapper.map(p1, Person.class);
  8.  
    }
  9.  
    System.out.println("dozer:"+(System.currentTimeMillis()-start));
  10.  
    //dozer:721
  11.  
    }
  12.  
    @Test
  13.  
    public void testBatchBeanUtils(){
  14.  
    Long start=System.currentTimeMillis();
  15.  
    for(int i=0;i<10000;i++){
  16.  
    Person p1=PersonFactory.newPrototypeInstance();
  17.  
    try {
  18.  
    Person p2=(Person) BeanUtils.cloneBean(p1);
  19.  
    } catch (Exception e) {
  20.  
    e.printStackTrace();
  21.  
    }
  22.  
    }
  23.  
    System.out.println("commons-beanutils:"+(System.currentTimeMillis()-start));
  24.  
    //commons-beanutils:229
  25.  
    }
  26.  
    @Test
  27.  
    public void testBatchCglib(){
  28.  
    Long start=System.currentTimeMillis();
  29.  
    for(int i=0;i<10000;i++){
  30.  
    Person p1=PersonFactory.newPrototypeInstance();
  31.  
    BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
  32.  
    Person p2=new Person();
  33.  
    beanCopier.copy(p1, p2, new Converter(){
  34.  
    @Override
  35.  
    public Object convert(Object value, Class target, Object context) {
  36.  
    if(target.isSynthetic()){
  37.  
    BeanCopier.create(target, target, true).copy(value, value, this);
  38.  
    }
  39.  
    return value;
  40.  
    }
  41.  
    });
  42.  
    }
  43.  
    System.out.println("cglib:"+(System.currentTimeMillis()-start));
  44.  
    //cglib:133
  45.  
    }
  46.  
    @Test
  47.  
    public void testBatchSerial(){
  48.  
    Long start=System.currentTimeMillis();
  49.  
    for(int i=0;i<10000;i++){
  50.  
    Person p1=PersonFactory.newPrototypeInstance();
  51.  
    Person p2=p1.deepClone();
  52.  
    }
  53.  
    System.out.println("serializable:"+(System.currentTimeMillis()-start));
  54.  
    //serializable:687
  55.  
    }
  56.  
    @Test
  57.  
    public void testBatchOrika() {
  58.  
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
  59.  
     
  60.  
    mapperFactory.classMap(Person.class, Person.class)
  61.  
    .field("name", "name")
  62.  
    .byDefault()
  63.  
    .register();
  64.  
    ConverterFactory converterFactory = mapperFactory.getConverterFactory();
  65.  
    MapperFacade mapper = mapperFactory.getMapperFacade();
  66.  
     
  67.  
    Long start=System.currentTimeMillis();
  68.  
    for(int i=0;i<10000;i++){
  69.  
    Person p1=PersonFactory.newPrototypeInstance();
  70.  
    Person p2 = mapper.map(p1, Person.class);
  71.  
    }
  72.  
    System.out.println("orika:"+(System.currentTimeMillis()-start));
  73.  
    //orika:83
  74.  
    }
  75.  
     
  76.  
    @Test
  77.  
    public void testBatchClone(){
  78.  
    Long start=System.currentTimeMillis();
  79.  
    for(int i=0;i<10000;i++){
  80.  
    Person p1=PersonFactory.newPrototypeInstance();
  81.  
    try {
  82.  
    Person p2=(Person) p1.clone();
  83.  
    } catch (CloneNotSupportedException e) {
  84.  
    e.printStackTrace();
  85.  
    }
  86.  
    }
  87.  
    System.out.println("clone:"+(System.currentTimeMillis()-start));
  88.  
    //clone:8
  89.  
    }

(10k)性能比较:

  1.  
    //dozer:721
  2.  
    //commons-beanutils:229
  3.  
    //cglib:133
  4.  
    //serializable:687
  5.  
    //orika:83
  6.  
    //clone:8

深拷贝总结

原生的clone效率无疑是最高的,用脚趾头都能想到。

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

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

另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。

转载自https://blog.csdn.net/54powerman/article/details/64920431?locationNum=6&fps=1

有想学云计算的朋友可以看看这篇https://blog.csdn.net/qq_33314107/article/details/90677795

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!