原型模式(Prototype Pattern)之浅拷贝和深拷贝

浅拷贝和深拷贝

《设计模式之创建型模式中的原型模式(Prototype Pattern)》中,通过克隆羊多莉的例子了解了原型模式,从客户端代码运行的结果,很容易发现:通过原型对象创建另一个新对象时,如果更改原型对象的某些类型的属性,新建的对象的属性也可能会发生变化。

浅拷贝

通过原型对象创建另一个新对象时,将原型对象的非静态成员变量复制到新的对象,对于不同类型的成员变量,拷贝规则如下:

  1. 如果成员变量的数据类型是值类型(基本数据类型),浅拷贝会直接进行值传递,也就是直接复制一份该属性给新对象;
  2. 如果成员变量的数据类型是引用类型,则浅拷贝会进行引用传递,也就是只会将该成员变量的引用值(内存地址)复制一份给新对象,而不会复制引用的对象,那么也就意味着新对象和原型对象的该成员变量都指向同一个实例。因此,一个对象中修改成员变量的值会影响到另一个对象的该成员变量的值。

上一文我们举的例子就是浅拷贝实现克隆羊,SheepPrototype类实现了Cloneable接口,使用了默认的super.clone()方法。

  • 基本类型也称为值类型,分别是字符类型 char,布尔类型 boolean以及数值类型 byte、short、int、long、float、double。
  • 引用类型则包括类、接口、数组、枚举等。
  • Java 将内存空间分为堆和栈。基本类型直接在栈中存储数值,而引用类型是将引用放在栈中,实际存储的值是放在堆中,通过栈中的引用指向堆中存放的数据。

深拷贝

通过原型对象创建另一个新对象时,将原型对象的非静态成员变量复制到新的对象,不管成员变量的数据类型是值类型还是引用类型,深拷贝都会重新复制一份给新的对象。因此,修改其中一个对象的任何成员变量的值,都不会影响到另一个对象。

深拷贝的实现

一、重写clone() 方法
重写clone()方法,把要复制的对象所引用的对象都复制一遍,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class DeepConcreteSheepPrototype  implements Cloneable{
private String name;
private int age;
private String color;
private SheepPrototype mother;

public DeepConcreteSheepPrototype(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}

public DeepConcreteSheepPrototype() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

public SheepPrototype getMother() {
return mother;
}

public void setMother(SheepPrototype mother) {
this.mother = mother;
}
/**
* 克隆实例
* @return
*/
@Override
protected DeepConcreteSheepPrototype clone() {
DeepConcreteSheepPrototype deepConcreteSheepPrototype = null;
try {
deepConcreteSheepPrototype = (DeepConcreteSheepPrototype) super.clone();
SheepPrototype sheepPrototype = mother.clone();
deepConcreteSheepPrototype.mother = sheepPrototype;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return deepConcreteSheepPrototype;
}

@Override
public String toString() {
return "DeepConcreteSheepPrototype{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", mother=" + mother +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CloneDollyPrototype {

static class Client{
public static void main(String[] args) {
DeepConcreteSheepPrototype deepDolly = new DeepConcreteSheepPrototype("dolly",2,"gray");
deepDolly.setMother(new ConcreteSheepPrototype("dolly",5,"gray"));
DeepConcreteSheepPrototype deepConcreteSheepPrototype = deepDolly.clone();
DeepConcreteSheepPrototype deepConcreteSheepPrototype1 = deepDolly.clone();
DeepConcreteSheepPrototype deepConcreteSheepPrototypeN = deepDolly.clone();
deepDolly.getMother().setColor("red");
System.out.println(deepDolly);
System.out.println(deepConcreteSheepPrototype);
System.out.println(deepConcreteSheepPrototype1);
System.out.println(deepConcreteSheepPrototypeN);
}
}
}

运行结果如下:
重写clone()方法实现深拷贝

二、利用序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class DeepConcreteSheepSerializable implements Serializable {
private static final long serialVersionUID = 8738439006982997247L;
private String name;
private int age;
private String color;
private SheepPrototype mother;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

public SheepPrototype getMother() {
return mother;
}

public void setMother(SheepPrototype mother) {
this.mother = mother;
}

public Object deepClone(){
ByteArrayOutputStream out = null;
ObjectOutputStream obs = null;
ByteArrayInputStream ios = null;
ObjectInputStream ois = null;
try {
//序列化
out = new ByteArrayOutputStream();
obs = new ObjectOutputStream(out);
obs.writeObject(this);
obs.close();

//反序列化
ios = new ByteArrayInputStream(out.toByteArray());
ois = new ObjectInputStream(ios);
//返回生成的新对象
DeepConcreteSheepSerializable deepConcreteSheepSerializable = (DeepConcreteSheepSerializable) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
obs.close();
ios.close();
ois.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
return null;
}
@Override
public String toString() {
return "DeepConcreteSheepSerializable{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", mother=" + mother +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CloneDollyPrototype {

static class Client{
public static void main(String[] args) {
DeepConcreteSheepSerializable deepDollySerializable = new DeepConcreteSheepSerializable("dolly",2,"gray");
deepDollySerializable.setMother(new ConcreteSheepPrototype("dolly",5,"gray"));
DeepConcreteSheepSerializable deepConcreteSheepSerializable = deepDollySerializable.deepClone();
deepDollySerializable.getMother().setColor("red");
deepDollySerializable.setAge(6);
System.out.println(deepDollySerializable);
System.out.println(deepConcreteSheepSerializable);
}
}
}

运行结果:
利用序列化实现深拷贝

从上面可以看出,利用序列化实现深拷贝主要是在内存中通过字节流的拷贝来实现的。把原型对象序列化写入到字节流中,然后再从字节流中将其读出来进行反序列化,这样就可以创建一个新的对象,当然,此种方式不会存在原型对象与新对象之间引用共享的问题,推荐使用这种方式实现深拷贝。

附:本次演示的项目地址
https://github.com/syshlang/java-design-patterns