一、对象的克隆(拷贝)
克隆的对象包含一些已经修改过的属性,而 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接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。