一、对象的克隆(拷贝)

  克隆的对象包含一些已经修改过的属性,而 new 出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠克隆方法了。

二、克隆分类

  1、克隆对象前提



protected native Object clone() throws CloneNotSupportedException;



    该方法被native修饰,告诉 JVM 自己去调用。当我们在自定义类中使用该方法的时候,需要继承一个 Cloneable 接口,否则会抛出无法克隆的异常。该方法是一个浅复制,不是深复制。

  2、浅拷贝

       浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

  3、深拷贝

    深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

三、浅拷贝的实现

  拷贝的前提:实现一个 Cloneable 接口,该接口只是一个标记接口,里面并没有具体的方法。



public interface Cloneable {}



  实现方式:实现 Cloneable 接口并重写 Object 类中的 clone() 方法

  声明一个 Worker 类,其中有一个name成员变量:



1//克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法 2class Worker implements Cloneable{
 3private String name;
 4 5public Worker(String name) {
 6super();
 7this.name = name;
 8    }
 910public Worker() {
11super();
12    }
13public String getName() {
14return name;
15    }
16publicvoid setName(String name) {
17this.name = name;
18    }
19    @Override
20public String toString() {
21return "Worker [name=" + name + "]";
22    }
23    @Override
24protected Object clone() throws CloneNotSupportedException {
25returnsuper.clone();
26    }
2728}
2930publicstaticvoid main(String[] args) throws Exception {
3132         Worker worker1 = new Worker("张三");
3334         Worker worker2 = (Worker) ();
3536        System.out.println(());
37        System.out.println(());
3839         System.out.println("==============");
40//克隆后得到的是一个新的对象,所以重新写的是student2这个对象的值41         worker2.setName("李四");
4243        System.out.println(());
44        System.out.println(());
4546    }
4748 运行结果:
49  Worker [name=张三]
50  Worker [name=张三]
51  ==============
52  Worker [name=张三]
53  Worker [name=李四]



  图解说明:

     

hutool copyproperties是深拷贝吗 clone深拷贝还是浅拷贝_java clone方法

    此时,worker1 与 worker2 是两个不同的对象,修改 worker2的值并不影响 worker1.

      注意:

    

hutool copyproperties是深拷贝吗 clone深拷贝还是浅拷贝_System_02

    这是只是进行赋值操作,用两个指针指向同一个对象,并不是复制对象!

   上面的 Demo 中 Worker 只有一个基本类型的成员变量,如果再添加一个引用类型的成员变量呢?

    如果在 Worker 类中添加一个 Address 属性呢?



1//如果在 Worker 类中添加一个 Address 属性呢?
 2//克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法 3class Worker2 implements Cloneable{
 4private String name;
 5 6private Address addr;
 7 8 9public Worker2(String name, Address addr) {
10super();
11this.name = name;
12this.addr = addr;
13    }
1415public Worker2() {
16super();
17    }
1819public String getName() {
20return name;
21    }
2223publicvoid setName(String name) {
24this.name = name;
25    }
262728public Address getAddr() {
29return addr;
30    }
3132publicvoid setAddr(Address addr) {
33this.addr = addr;
34    }
35    @Override
36public String toString() {
37return "Worker2 [name=" + name + ", addr=" + addr + "]";
38    }
3940    @Override
41protected Object clone() throws CloneNotSupportedException {
42returnsuper.clone();
43    }
44}
4546class Address {
47private String city;
484950public Address() {
51super();
52    }
5354public Address(String city) {
55super();
56this.city = city;
57    }
5859public String getCity() {
60return city;
61    }
6263publicvoid setCity(String city) {
64this.city = city;
65    }
6667    @Override
68public String toString() {
69return "Address [city=" + city + "]";
70    }
71}
72//主方法73publicstaticvoid main(String[] args) throws Exception {
7475         Address addr = new Address("北京");
76         Worker2 worker1 = new Worker2("张三", addr);
7778         Worker2 worker2 = (Worker2) ();
7980        System.out.println(());
81        System.out.println(());
8283         System.out.println("==============");
84//克隆后得到的是一个新的对象,所以重新写的是student2这个对象的值85         worker2.setName("李四");
8687         addr.setCity("上海");
8889        System.out.println(());
90        System.out.println(());
9192    }
9394 运行结果:
95  Worker2 [name=张三, addr=Address [city=北京]]
96 Worker2 [name=张三, addr=Address [city=北京]]
97 ==============
98 Worker2 [name=张三, addr=Address [city=上海]]
99 Worker2 [name=李四, addr=Address [city=上海]]



  这时发现与我们想象的并不一样, Worker2 不是由 Worker1 复制的,与 Worker1 没有关系吗?

  这时由于进行的是浅复制,对于 引用数据类型来说,并没有进行复制。

  图解:



  

hutool copyproperties是深拷贝吗 clone深拷贝还是浅拷贝_java clone方法_03

    对于浅克隆来说,复制 worker1 这个对象,只会复制基本数据类型的成员变量,而 Address 是一个引用数据类型的变量,它address 的指向还是 worker1 中的 address 对象,两个共用一个 Address 对象,所以当 worker1 修改 address 属性时,worker2 也会随着更改。



四、深拷贝的实现

  1、方式一:手动依次实现 clone() 方法



1//克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法  2class Worker3 implements Cloneable{
  3private String name;
  4  5private Address3 addr;
  6  7  8public Worker3(String name, Address3 addr) {
  9super();
 10this.name = name;
 11this.addr = addr;
 12    }
 13 14public Worker3() {
 15super();
 16    }
 17 18public String getName() {
 19return name;
 20    }
 21 22publicvoid setName(String name) {
 23this.name = name;
 24    }
 25 26 27public Address3 getAddr() {
 28return addr;
 29    }
 30 31publicvoid setAddr(Address3 addr) {
 32this.addr = addr;
 33    }
 34 35 36 37    @Override
 38public String toString() {
 39return "Worker3 [name=" + name + ", addr=" + addr + "]";
 40    }
 41 42//    @Override
 43//    protected Object clone() throws CloneNotSupportedException {
 44// 45//        Worker3 worker3 = (Worker3) ();   //浅复制
 46// 47//        Address3 addr3 = (Address3) ().clone(); //深复制
 48// 49//        (addr3);
 50// 51//        return worker3;
 52//    } 53 54    @Override
 55protected Object clone() throws CloneNotSupportedException {
 56 57         Worker3 worker3 = null;
 58 59         worker3 = (Worker3) super.clone();   //浅复制
 60 61//worker3.addr = (Address3) ().clone(); //深复制 62         worker3.addr = (Address3) (); //深复制 63 64return worker3;
 65    }
 66 67 68 69}
 70 71class Address3 implements Cloneable{
 72private String city;
 73 74 75public Address3() {
 76super();
 77    }
 78 79public Address3(String city) {
 80super();
 81this.city = city;
 82    }
 83 84public String getCity() {
 85return city;
 86    }
 87 88publicvoid setCity(String city) {
 89this.city = city;
 90    }
 91 92    @Override
 93public String toString() {
 94return "Address3 [city=" + city + "]";
 95    }
 96 97    @Override
 98protected Object clone() throws CloneNotSupportedException {
 99returnsuper.clone();
100    }
101102}
103//主方法104publicstaticvoid main(String[] args) throws Exception {
105106         Address3 addr = new Address3("北京");
107         Worker3 worker1 = new Worker3("张三", addr);
108109         Worker3 worker2 = (Worker3) ();
110111        System.out.println(());
112        System.out.println(());
113114         System.out.println("==============");
115//克隆后得到的是一个新的对象,所以重新写的是worker2这个对象的值116         worker2.setName("李四");
117118         addr.setCity("上海");
119120        System.out.println(());
121        System.out.println(());
122123    }
124125 运行结果:
126  Worker3 [name=张三, addr=Address3 [city=北京]]
127 Worker3 [name=张三, addr=Address3 [city=北京]]
128 ==============
129 Worker3 [name=张三, addr=Address3 [city=上海]]
130 Worker3 [name=李四, addr=Address3 [city=北京]]



    此时就实现了基本数据类型与引用数据类型全部进行了复制。

  2、方式二:实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。



1class Student implements Serializable {
  2  3privatestaticfinallong serialVersionUID = 1L;
  4  5privateint age;
  6private String name;
  7private Teacher teacher;
  8  9publicint getAge() {
 10return age;
 11    }
 12publicvoid setAge(int age) {
 13this.age = age;
 14    }
 15public String getName() {
 16return name;
 17    }
 18publicvoid setName(String name) {
 19this.name = name;
 20    }
 21public Teacher getTeacher() {
 22return teacher;
 23    }
 24publicvoid setTeacher(Teacher teacher) {
 25this.teacher = teacher;
 26    }
 27    @Override
 28public String toString() {
 29return "Student [age=" + age + ", name=" + name + ", teacher=" + teacher + "]";
 30    }
 31 32//使得序列化student3的时候也会将teacher序列化 33public Object deepCopt()throws Exception {
 34 35         ByteArrayOutputStream bos = new ByteArrayOutputStream();
 36         ObjectOutputStream  oos = new ObjectOutputStream(bos);
 37         oos.writeObject(this);
 38//将当前这个对象写到一个输出流当中,,因为这个对象的类实现了Serializable这个接口,所以在这个类中
 39//有一个引用,这个引用如果实现了序列化,那么这个也会写到这个输出流当中 40 41         ByteArrayInputStream bis = new ByteArrayInputStream(());
 42         ObjectInputStream ois = new ObjectInputStream(bis);
 43return ();
 44//这个就是将流中的东西读出类,读到一个对象流当中,这样就可以返回这两个对象的东西,实现深克隆 45    }
 46 47}
 48 49class Teacher implements Serializable {
 50 51privateint age;
 52private String name;
 53 54publicint getAge() {
 55return age;
 56    }
 57publicvoid setAge(int age) {
 58this.age = age;
 59    }
 60public String getName() {
 61return name;
 62    }
 63publicvoid setName(String name) {
 64this.name = name;
 65    }
 66    @Override
 67public String toString() {
 68return "Teacher [age=" + age + ", name=" + name + "]";
 69    }
 70 71}
 72//主方法 73publicstaticvoid main(String[] args) throws Exception {
 74         Teacher t1 = new Teacher();
 75         t1.setAge(33);
 76         t1.setName("王老师");
 77 78         Student stu1 = new Student();
 79         stu1.setAge(22);
 80         stu1.setName("张三");
 81        stu1.setTeacher(t1);
 82 83         Student stu2 = (Student) ();
 84        System.out.println(stu1);
 85        System.out.println(stu2);
 86 87         System.out.println("===============");
 88 89         stu2.getTeacher().setAge(44);
 90         stu2.getTeacher().setName("李老师");
 91 92         stu2.setAge(23);
 93         stu2.setName("李四");
 94 95        System.out.println(stu1);
 96        System.out.println(stu2);
 97 98    }
 99100 运行结果:
101  Student [age=22, name=张三, teacher=Teacher [age=33, name=王老师]]
102 Student [age=22, name=张三, teacher=Teacher [age=33, name=王老师]]
103 ===============
104 Student [age=22, name=张三, teacher=Teacher [age=33, name=王老师]]
105 Student [age=23, name=李四, teacher=Teacher [age=44, name=李老师]]



  序列化克隆:

    序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

    利用serializable实现深复制(这个是利用Serializable,利用序列化的方式来实现深复制(深克隆),在其中利用了Io流的方式将这个对象写到IO流里面,然后在从IO流里面读取,这样就实现了一个复制,然后实现序列化的这个会将引用的那个对象也一并进行深复制,这样就实现了这个机制,同时在IO里面读取数据的时候还使用了装饰者模式)



  这种方式可以解决多层克隆问题:

如果引用类型里面还包含很多引用类型,或者内层引用的类里面又包含引用类型,使用 clone() 方法就会很麻烦,这时就可以使用序列化和反序列化的方式实现对象的深克隆。



五、封装克隆工具

  可以将对象克隆封装成一个工具类:



1import java.io.ByteArrayInputStream;
 2import java.io.ByteArrayOutputStream;
 3import java.io.ObjectInputStream;
 4import java.io.ObjectOutputStream;
 5import java.io.Serializable;
 6 7publicclass CloneUtil {
 8 9     @SuppressWarnings("unchecked")
10publicstatic <T extends Serializable> T clone(T object) throws Exception{
11//写入字节流12         ByteArrayOutputStream bout = new ByteArrayOutputStream();
13         ObjectOutputStream oos = new ObjectOutputStream(bout);
14        oos.writeObject(object);
1516//分配内存,写入原始对象,生成新对象17         ByteArrayInputStream bin = new ByteArrayInputStream(());
18         ObjectInputStream ois = new ObjectInputStream(bin);
19// 此处不需要释放资源,说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
20// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放21return (T) ();
22    }
2324 }



六、总结

  实现对象克隆有两种方式:

    1. 实现Cloneable接口并重写Object类中的clone()方法;

    2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。