java image clone_Java 之 clone 方法(对象拷贝)

一、对象的克隆(拷贝)

克隆的对象包含一些已经修改过的属性,而 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=李四]

图解说明:

c9c1bb5616462552615d86b3429e077b.png

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

注意:

2724cbfacd5bc8877e3896740fcbc795.png

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

13948752.html

上面的 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 没有关系吗?

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

图解:

22381d3a1372b7c9669cc693ff80c1ff.png

对于浅克隆来说,复制 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接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值