当前位置: 首页 > news >正文

java对象拷贝最完全解说

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

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 
版权声明:本文为博主原创文章,转载请附上博文链接!

转载于:https://my.oschina.net/u/3005325/blog/3006719

相关文章:

  • JVM,DVM,ART
  • 微软工程师认为 Mozilla 也应该拥抱 Chromium
  • 司法部:做好春节期间在押罪犯的离监探亲工作
  • 斯内德将出任2020欧洲杯荷兰地区形象大使
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • 使用Jmeter输出错误响应结果到日志
  • Hunt framework 2.0.0 发布,简单且高性能的 Web 服务框架
  • 补贴退坡幅度进一步加大 新能源汽车会涨价吗
  • Linux基础_软件包管理
  • Nginx配置文件的高亮显示设置
  • 【leetcode】983. Minimum Cost For Tickets
  • BZOJ 2810 [Apio2012]kunai
  • HashMap剖析之内部结构
  • OpenvSwitch/OpenFlow 架构解析与实践案例
  • CSS opacity设置不透明度
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • exif信息对照
  • SSH 免密登录
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • Yeoman_Bower_Grunt
  • 浮动相关
  • 给github项目添加CI badge
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 使用agvtool更改app version/build
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • ​linux启动进程的方式
  • (07)Hive——窗口函数详解
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (SpringBoot)第二章:Spring创建和使用
  • (八)Spring源码解析:Spring MVC
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (译)2019年前端性能优化清单 — 下篇
  • (转)一些感悟
  • .net程序集学习心得
  • .net访问oracle数据库性能问题
  • .NET性能优化(文摘)
  • @configuration注解_2w字长文给你讲透了配置类为什么要添加 @Configuration注解
  • @JoinTable会自动删除关联表的数据
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节
  • []FET-430SIM508 研究日志 11.3.31
  • [100天算法】-每个元音包含偶数次的最长子字符串(day 53)
  • [2021]Zookeeper getAcl命令未授权访问漏洞概述与解决
  • [ai笔记9] openAI Sora技术文档引用文献汇总
  • [C++] Windows中字符串函数的种类
  • [C语言]一维数组二维数组的大小
  • [Dxperience.8.*]报表预览控件PrintControl设置
  • [EFI]Lenovo ThinkPad X280电脑 Hackintosh 黑苹果引导文件
  • [GDMEC-无人机遥感研究小组]无人机遥感小组-000-数据集制备
  • [IE技巧] 如何关闭Windows Server版IE的安全限制
  • [JavaScript]_[初级]_[关于forin或for...in循环语句的用法]
  • [KMP求最小循环节][HDU1358][Period]
  • [MFC] MFC 获取指定窗口截图(大小可调)