一、对象的克隆(拷贝)
克隆的对象包含一些已经修改过的属性,而 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=李四]
图解说明:
此时,worker1 与 worker2 是两个不同的对象,修改 worker2的值并不影响 worker1.
注意:
这是只是进行赋值操作,用两个指针指向同一个对象,并不是复制对象!
上面的 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 没有关系吗?
这时由于进行的是浅复制,对于 引用数据类型来说,并没有进行复制。
图解:
对于浅克隆来说,复制 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接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。