一、对象的克隆(拷贝)
克隆的对象包含一些已经修改过的属性,而 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 implementsCloneable{3 privateString name;4
5 publicWorker(String name) {6 super();7 this.name =name;8 }9
10 publicWorker() {11 super();12 }13 publicString getName() {14 returnname;15 }16 public voidsetName(String name) {17 this.name =name;18 }19 @Override20 publicString toString() {21 return "Worker [name=" + name + "]";22 }23 @Override24 protected Object clone() throwsCloneNotSupportedException {25 return super.clone();26 }27
28 }29
30 public static void main(String[] args) throwsException {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 implementsCloneable{4 privateString name;5
6 privateAddress addr;7
8
9 publicWorker2(String name, Address addr) {10 super();11 this.name =name;12 this.addr =addr;13 }14
15 publicWorker2() {16 super();17 }18
19 publicString getName() {20 returnname;21 }22
23 public voidsetName(String name) {24 this.name =name;25 }26
27
28 publicAddress getAddr() {29 returnaddr;30 }31
32 public voidsetAddr(Address addr) {33 this.addr =addr;34 }35 @Override36 publicString toString() {37 return "Worker2 [name=" + name + ", addr=" + addr + "]";38 }39
40 @Override41 protected Object clone() throwsCloneNotSupportedException {42 return super.clone();43 }44 }45
46 classAddress {47 privateString city;48
49
50 publicAddress() {51 super();52 }53
54 publicAddress(String city) {55 super();56 this.city =city;57 }58
59 publicString getCity() {60 returncity;61 }62
63 public voidsetCity(String city) {64 this.city =city;65 }66
67 @Override68 publicString toString() {69 return "Address [city=" + city + "]";70 }71 }72 //主方法
73 public static void main(String[] args) throwsException {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 implementsCloneable{3 privateString name;4
5 privateAddress3 addr;6
7
8 publicWorker3(String name, Address3 addr) {9 super();10 this.name =name;11 this.addr =addr;12 }13
14 publicWorker3() {15 super();16 }17
18 publicString getName() {19 returnname;20 }21
22 public voidsetName(String name) {23 this.name =name;24 }25
26
27 publicAddress3 getAddr() {28 returnaddr;29 }30
31 public voidsetAddr(Address3 addr) {32 this.addr =addr;33 }34
35
36
37 @Override38 publicString toString() {39 return "Worker3 [name=" + name + ", addr=" + addr + "]";40 }41
42 //@Override43 //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 @Override55 protected Object clone() throwsCloneNotSupportedException {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 returnworker3;65 }66
67
68
69 }70
71 class Address3 implementsCloneable{72 privateString city;73
74
75 publicAddress3() {76 super();77 }78
79 publicAddress3(String city) {80 super();81 this.city =city;82 }83
84 publicString getCity() {85 returncity;86 }87
88 public voidsetCity(String city) {89 this.city =city;90 }91
92 @Override93 publicString toString() {94 return "Address3 [city=" + city + "]";95 }96
97 @Override98 protected Object clone() throwsCloneNotSupportedException {99 return super.clone();100 }101
102 }103 //主方法
104 public static void main(String[] args) throwsException {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 implementsSerializable {2
3 private static final long serialVersionUID = 1L;4
5 private intage;6 privateString name;7 privateTeacher teacher;8
9 public intgetAge() {10 returnage;11 }12 public void setAge(intage) {13 this.age =age;14 }15 publicString getName() {16 returnname;17 }18 public voidsetName(String name) {19 this.name =name;20 }21 publicTeacher getTeacher() {22 returnteacher;23 }24 public voidsetTeacher(Teacher teacher) {25 this.teacher =teacher;26 }27 @Override28 publicString toString() {29 return "Student [age=" + age + ", name=" + name + ", teacher=" + teacher + "]";30 }31
32 //使得序列化student3的时候也会将teacher序列化
33 public Object deepCopt()throwsException {34
35 ByteArrayOutputStream bos = newByteArrayOutputStream();36 ObjectOutputStream oos = newObjectOutputStream(bos);37 oos.writeObject(this);38 //将当前这个对象写到一个输出流当中,,因为这个对象的类实现了Serializable这个接口,所以在这个类中39 //有一个引用,这个引用如果实现了序列化,那么这个也会写到这个输出流当中
40
41 ByteArrayInputStream bis = newByteArrayInputStream(bos.toByteArray());42 ObjectInputStream ois = newObjectInputStream(bis);43 returnois.readObject();44 //这个就是将流中的东西读出类,读到一个对象流当中,这样就可以返回这两个对象的东西,实现深克隆
45 }46
47 }48
49 class Teacher implementsSerializable {50
51 private intage;52 privateString name;53
54 public intgetAge() {55 returnage;56 }57 public void setAge(intage) {58 this.age =age;59 }60 publicString getName() {61 returnname;62 }63 public voidsetName(String name) {64 this.name =name;65 }66 @Override67 publicString toString() {68 return "Teacher [age=" + age + ", name=" + name + "]";69 }70
71 }72 //主方法
73 public static void main(String[] args) throwsException {74 Teacher t1 = newTeacher();75 t1.setAge(33);76 t1.setName("王老师");77
78 Student stu1 = newStudent();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 importjava.io.ByteArrayInputStream;2 importjava.io.ByteArrayOutputStream;3 importjava.io.ObjectInputStream;4 importjava.io.ObjectOutputStream;5 importjava.io.Serializable;6
7 public classCloneUtil {8
9 @SuppressWarnings("unchecked")10 public static T clone(T object) throwsException{11 //写入字节流
12 ByteArrayOutputStream bout = newByteArrayOutputStream();13 ObjectOutputStream oos = newObjectOutputStream(bout);14 oos.writeObject(object);15
16 //分配内存,写入原始对象,生成新对象
17 ByteArrayInputStream bin = newByteArrayInputStream(bout.toByteArray());18 ObjectInputStream ois = newObjectInputStream(bin);19 //此处不需要释放资源,说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义20 //这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
21 return(T) ois.readObject();22 }23
24 }
六、总结
实现对象克隆有两种方式:
1. 实现Cloneable接口并重写Object类中的clone()方法;
2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。