一、对象的克隆(拷贝)

  克隆的对象包含一些已经修改过的属性,而 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方法
 2 class Worker implements Cloneable{
 3     private String name;
 4 
 5     public Worker(String name) {
 6         super();
 7         this.name = name;
 8     }
 9 
10     public Worker() {
11         super();
12     }
13     public String getName() {
14         return name;
15     }
16     public void setName(String name) {
17         this.name = name;
18     }
19     @Override
20     public String toString() {
21         return "Worker [name=" + name + "]";
22     }
23     @Override
24     protected Object clone() throws CloneNotSupportedException {
25         return super.clone();
26     }
27     
28 }
29 
30 public static void main(String[] args) throws Exception {
31         
32         Worker worker1 = new Worker("张三");
33         
34         Worker worker2 = (Worker) worker1.clone();
35         
36         System.out.println(worker1.toString());
37         System.out.println(worker2.toString());
38         
39         System.out.println("==============");
40         //克隆后得到的是一个新的对象,所以重新写的是student2这个对象的值
41         worker2.setName("李四");
42         
43         System.out.println(worker1.toString());
44         System.out.println(worker2.toString());
45         
46     }
47  
48  运行结果:
49  Worker [name=张三]
50  Worker [name=张三]
51  ==============
52  Worker [name=张三]
53  Worker [name=李四]

 

  图解说明:

     

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

      注意:

    

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

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

    如果在 Worker 类中添加一个 Address 属性呢?
 1 //如果在 Worker 类中添加一个 Address 属性呢?
 2 //克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法
 3 class Worker2 implements Cloneable{
 4     private String name;
 5     
 6     private Address addr;
 7 
 8 
 9     public Worker2(String name, Address addr) {
10         super();
11         this.name = name;
12         this.addr = addr;
13     }
14 
15     public Worker2() {
16         super();
17     }
18 
19     public String getName() {
20         return name;
21     }
22 
23     public void setName(String name) {
24         this.name = name;
25     }
26     
27 
28     public Address getAddr() {
29         return addr;
30     }
31 
32     public void setAddr(Address addr) {
33         this.addr = addr;
34     }
35     @Override
36     public String toString() {
37         return "Worker2 [name=" + name + ", addr=" + addr + "]";
38     }
39 
40     @Override
41     protected Object clone() throws CloneNotSupportedException {
42         return super.clone();
43     }
44 }
45 
46 class Address {
47     private String city;
48     
49     
50     public Address() {
51         super();
52     }
53 
54     public Address(String city) {
55         super();
56         this.city = city;
57     }
58 
59     public String getCity() {
60         return city;
61     }
62 
63     public void setCity(String city) {
64         this.city = city;
65     }
66 
67     @Override
68     public String toString() {
69         return "Address [city=" + city + "]";
70     }
71 }
72 //主方法
73 public static void main(String[] args) throws Exception {
74         
75         Address addr = new Address("北京");
76         Worker2 worker1 = new Worker2("张三", addr);
77         
78         Worker2 worker2 = (Worker2) worker1.clone();
79         
80         System.out.println(worker1.toString());
81         System.out.println(worker2.toString());
82         
83         System.out.println("==============");
84         //克隆后得到的是一个新的对象,所以重新写的是student2这个对象的值
85         worker2.setName("李四");
86         
87         addr.setCity("上海");
88         
89         System.out.println(worker1.toString());
90         System.out.println(worker2.toString());
91         
92     }
93  
94  运行结果:
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 没有关系吗?
  这时由于进行的是浅复制,对于 引用数据类型来说,并没有进行复制。
  图解:
  
    对于浅克隆来说,复制 worker1 这个对象,只会复制基本数据类型的成员变量,而 Address 是一个引用数据类型的变量,它address 的指向还是 worker1 中的 address 对象,两个共用一个 Address 对象,所以当 worker1 修改 address 属性时,worker2 也会随着更改。

四、深拷贝的实现

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

  1 //克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法
  2 class Worker3 implements Cloneable{
  3     private String name;
  4     
  5     private Address3 addr;
  6 
  7 
  8     public Worker3(String name, Address3 addr) {
  9         super();
 10         this.name = name;
 11         this.addr = addr;
 12     }
 13 
 14     public Worker3() {
 15         super();
 16     }
 17 
 18     public String getName() {
 19         return name;
 20     }
 21 
 22     public void setName(String name) {
 23         this.name = name;
 24     }
 25     
 26 
 27     public Address3 getAddr() {
 28         return addr;
 29     }
 30 
 31     public void setAddr(Address3 addr) {
 32         this.addr = addr;
 33     }
 34 
 35     
 36 
 37     @Override
 38     public String toString() {
 39         return "Worker3 [name=" + name + ", addr=" + addr + "]";
 40     }
 41 
 42 //    @Override
 43 //    protected Object clone() throws CloneNotSupportedException {
 44 //        
 45 //        Worker3 worker3 = (Worker3) super.clone();   //浅复制
 46 //        
 47 //        Address3 addr3 = (Address3) worker3.getAddr().clone(); //深复制
 48 //        
 49 //        worker3.setAddr(addr3);
 50 //        
 51 //        return worker3;
 52 //    }
 53     
 54     @Override
 55     protected Object clone() throws CloneNotSupportedException {
 56         
 57         Worker3 worker3 = null;
 58         
 59         worker3 = (Worker3) super.clone();   //浅复制
 60         
 61         //worker3.addr = (Address3) worker3.getAddr().clone(); //深复制
 62         worker3.addr = (Address3) addr.clone(); //深复制
 63         
 64         return worker3;
 65     }
 66 
 67 
 68     
 69 }
 70 
 71 class Address3 implements Cloneable{
 72     private String city;
 73     
 74     
 75     public Address3() {
 76         super();
 77     }
 78 
 79     public Address3(String city) {
 80         super();
 81         this.city = city;
 82     }
 83 
 84     public String getCity() {
 85         return city;
 86     }
 87 
 88     public void setCity(String city) {
 89         this.city = city;
 90     }
 91 
 92     @Override
 93     public String toString() {
 94         return "Address3 [city=" + city + "]";
 95     }
 96 
 97     @Override
 98     protected Object clone() throws CloneNotSupportedException {
 99         return super.clone();
100     }
101     
102 }
103 //主方法
104 public static void main(String[] args) throws Exception {
105         
106         Address3 addr = new Address3("北京");
107         Worker3 worker1 = new Worker3("张三", addr);
108         
109         Worker3 worker2 = (Worker3) worker1.clone();
110         
111         System.out.println(worker1.toString());
112         System.out.println(worker2.toString());
113         
114         System.out.println("==============");
115         //克隆后得到的是一个新的对象,所以重新写的是worker2这个对象的值
116         worker2.setName("李四");
117         
118         addr.setCity("上海");
119         
120         System.out.println(worker1.toString());
121         System.out.println(worker2.toString());
122         
123     }
124  
125  运行结果:
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 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

  1 class Student implements Serializable {
  2 
  3     private static final long serialVersionUID = 1L;
  4     
  5     private int age;
  6     private String name;
  7     private Teacher teacher;
  8     
  9     public int getAge() {
 10         return age;
 11     }
 12     public void setAge(int age) {
 13         this.age = age;
 14     }
 15     public String getName() {
 16         return name;
 17     }
 18     public void setName(String name) {
 19         this.name = name;
 20     }
 21     public Teacher getTeacher() {
 22         return teacher;
 23     }
 24     public void setTeacher(Teacher teacher) {
 25         this.teacher = teacher;
 26     }
 27     @Override
 28     public String toString() {
 29         return "Student [age=" + age + ", name=" + name + ", teacher=" + teacher + "]";
 30     }
 31     
 32     //使得序列化student3的时候也会将teacher序列化
 33     public 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(bos.toByteArray());
 42         ObjectInputStream ois = new ObjectInputStream(bis);
 43         return ois.readObject();
 44         //这个就是将流中的东西读出类,读到一个对象流当中,这样就可以返回这两个对象的东西,实现深克隆
 45     }
 46 
 47 }
 48 
 49 class Teacher implements Serializable {
 50     
 51     private int age;
 52     private String name;
 53     
 54     public int getAge() {
 55         return age;
 56     }
 57     public void setAge(int age) {
 58         this.age = age;
 59     }
 60     public String getName() {
 61         return name;
 62     }
 63     public void setName(String name) {
 64         this.name = name;
 65     }
 66     @Override
 67     public String toString() {
 68         return "Teacher [age=" + age + ", name=" + name + "]";
 69     }
 70         
 71 }
 72 //主方法
 73 public static void 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) stu1.deepCopt();
 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     }
 99  
100  运行结果:
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() 方法就会很麻烦,这时就可以使用序列化和反序列化的方式实现对象的深克隆。

五、封装克隆工具

  可以将对象克隆封装成一个工具类:
 1 import java.io.ByteArrayInputStream;
 2 import java.io.ByteArrayOutputStream;
 3 import java.io.ObjectInputStream;
 4 import java.io.ObjectOutputStream;
 5 import java.io.Serializable;
 6 
 7 public class CloneUtil {
 8  
 9     @SuppressWarnings("unchecked")
10     public static <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);
15  
16         //分配内存,写入原始对象,生成新对象
17         ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
18         ObjectInputStream ois = new ObjectInputStream(bin);
19         // 此处不需要释放资源,说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
20         // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
21         return (T) ois.readObject();
22     }
23 
24 }

 

六、总结

  实现对象克隆有两种方式:
    1. 实现Cloneable接口并重写Object类中的clone()方法;
    2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

 

posted on 2020-11-09 15:21  格物致知_Tony  阅读(1215)  评论(0编辑  收藏  举报