引语

在 “值传递和引用传递” 这篇文章中我们提到了直接使用引用传递在方法中修改所指向对象的内容时实际是在修改实参,当我们想要复制对象进行修改而不影响原始对象时,我们就应根据实际需求选择浅拷贝或深拷贝。

深拷贝是指不仅复制对象本身,还递归复制对象中的所有引用类型成员变量所指向的对象,从而创建一个全新的、独立的对象。这样可以确保原始对象和克隆对象完全独立,对其中一个对象的修改不会影响到另一个对象。

概念

理解拷贝的概念前我们先做一个判断:

1
2
3
MyClass a = new MyClass();
MyClass b;
b = a;

这种方式中b是不是a的拷贝?
答案:否,b其实是指向a的一个引用,两个都对应内存中的同一个位置,因此修改b的值也会改动a的值。
在Java中拷贝对象(也称为克隆对象)有两种方式:浅拷贝和深拷贝。
拷贝要求拷贝出来的对象与原对象是独立的,浅拷贝和深拷贝与原对象都是独立的,区别仅在于:

  • 浅拷贝:只复制对象本身和基本数据类型成员变量,而不复制对象中引用类型的成员变量所指向的对象。
  • 深拷贝:不仅复制对象本身和基本数据类型成员变量,还递归复制对象中引用类型的成员变量所指向的对象。

特点

  • 浅拷贝:只复制对象本身和基本数据类型成员变量,不复制对象中引用类型成员变量所指向的对象,修改克隆对象的基本数据类型成员变量不改变原对象,而克隆对象和原对象的引用类型成员变量所指向的对象是相同的。
  • 深拷贝:不仅复制对象本身和基本数据类型成员变量,还递归复制对象中引用类型的成员变量所指向的对象,从而产生一个全新的独立于原对象的新对象。在新对象上做的所有修改都与原对象独立。

浅拷贝与深拷贝的主要区别只在对引用数据类型成员变量的处理上。
形象一点理解就是:

  • 浅拷贝只复制对象的当前层,引用类型成员变量所指向的对象就属于更深一层的内容了,因此在浅拷贝中不复制此内容
  • 深拷贝中会用递归的方式将所指向的对象也进行复制,从而产生完全一样完全独立的新对象。

浅拷贝

要完成浅拷贝,需要实现下面两个要求:

  1. 让自定义类实现Cloneable接口。
  2. 重写.clone()方法,返回值为Object类型。
  3. .clone()方法捕获或抛出CloneNotSupportedException异常
    下面是使用例子:
  4. 示例1:在.clone()方法中捕获处理异常。
    浅拷贝示例1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //自定义类
    class Class1 implements Cloneable{
    private Integer data;
    private Class2 next;

    @Override
    public Object clone() {
    Class1 cloned = null;
    try {
    cloned = (Class1)super.clone();
    }catch(CloneNotSupportedException e) {
    e.printStackTrace();
    }
    return cloned;
    }
    }
  5. 示例2:在.clone()方法抛出异常,在使用处捕获处理异常。
    浅拷贝示例2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //自定义类
    class Class1 implements Cloneable throws CloneNotSupportedException{
    private Integer data;
    private Class2 next;

    @Override
    public Object clone() {
    return (Class1)super.clone();
    }
    // 使用.clone()时用try-catch捕获并处理异常
    }

深拷贝

深拷贝相比浅拷贝多了一个手动完成引用类型成员变量的克隆操作。
如果想给对象提供浅拷贝和深拷贝两种方式,就需要单独书写一个深拷贝方法,示例:

深拷贝示例1
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
//自定义类
class Class1 implements Cloneable{
private Integer data;
private Class2 next;

@Override
public Object clone() { // 浅拷贝方式通过`.clone()`方式调用
Class1 cloned = null;
try {
cloned = (Class1) super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return cloned;
}

// 自定义方法省略

public Class1 deepClone() { // 深拷贝方式通过`.deepClone()`方式调用
Class1 cloned = null;
try {
cloned = (Class1)super.clone();
cloned.next = (Class2)next.clone(); // 深拷贝需要手动完成引用类型成员变量的递归复制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return cloned;
}
}

有时候我们针对一个对象只需要深拷贝这一种拷贝方法,那么就可以直接在.clone()方法中实现深拷贝,示例:

深拷贝示例2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//自定义类
class Class1 implements Cloneable{
private Integer data;
private Class2 next; // Class2也要实现Cloneable接口,并重写.clone()方法

@Override
public Object clone() { // 深拷贝通过`.clone()`方式调用
Class1 cloned = null;
try {
cloned = (Class1)super.clone();
cloned.next = (Class2)next.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return cloned;
}
// 自定义方法省略
}

两种深拷贝方式都是可以的,根据场景选择适合的。上面介绍的深拷贝都是通过克隆实现的,即实现Cloneable接口,还有一种方式是通过序列化和反序列化来完成深拷贝,需要实现``接口

注意:要实现深拷贝,引用类型成员变量的这个类(例如前文代码中的Class2)也要实现Cloneable接口,并重写.clone()方法。

序列化

有的场景下类中含有多个引用类型,或者引用类型内层还包含很多引用类型,那这种场景下如果还是用Cloneable接口的.clone()方式来书写就会特别麻烦,这时候可以考虑使用序列化来完成深度拷贝。
序列化(Serialization)就是将对象写到流的过程,写入完成后流中的对象是原有对象的一份拷贝,原对象仍然存在内存中,两者相互独立。对象序列化时不仅复制对象本身,还递归复制其引用的成员对象,因此通过序列化和反序列化过程就可以实现深拷贝。要完成深拷贝有下面的要求:

  1. 要实现序列化的对象类必须实现 Serializable 接口。
  2. 要实现序列化的对象中要复制的成员变量的对象类必须实现 Serializable 接口。
  3. 在深拷贝方法中包含对象的序列化和反序列化

具体实现示例:

序列化深拷贝
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
// 自定义类 要实现序列化的对象中要复制的成员变量的对象类也必须实现`Serializable`接口
class Class4 implements Serializable{
private String name;
// setter 和 getter 方法省略
}
// 自定义类 要实现序列化的对象类必须实现`Serializable`接口
class Class3 implements Serializable{
private Integer data;
private Class4 next; // 引用类型成员变量

public Class3 deepClone() {
Class3 cloned = null;
try { // 在深拷贝方法中捕获并处理异常
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
cloned = (Class3)ois.readObject();
}catch(IOException e) {
e.printStackTrace();
}catch(ClassNotFoundException e) {
e.printStackTrace();
}
return cloned;
}
// 自定义 setter 和 getter 方法省略
}

采用序列化实现的深拷贝与实现Cloneable接口的深拷贝有什么区别?
通过序列化和反序列化实现的深拷贝相比另一种深拷贝,多了“泛型限定”这一优势,编译器会在运行前检查待克隆的对象是否支持序列化,从而避免在运行时抛出异常。

tips: 当你想要让类中的某个成员变量不被深拷贝,也就是说不被序列化和反序列化,那就用transient关键字修饰该成员变量,或者将此成员变量加static修饰变为静态变量,因为静态变量归属于类,不会进入对象的序列化和反序列化过程。

总结

  1. 浅拷贝和深拷贝都可通过实现Cloneable接口,并重写.clone()来定义,两者区别在于深拷贝的引用成员变量的复制要手动完成。
  2. 深拷贝还可通过对象的序列化和反序列化来实现,相应的需要被拷贝的对象实现Serializable接口。
  3. 通过对象的序列化和反序列化实现的深拷贝能更好处理“引用嵌套,多个引用”这种情况,同时具备“泛型限定”的优势,在实现深拷贝时应优先采用这种方式。

附录

贴上完整测试代码:

完整测试代码
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class Class2 implements Cloneable{
private String name;

public Object clone() {
Class2 cloned = null;
try {
cloned = (Class2)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return cloned;
}

public void setName(String str) {
name = str;
}
public String getName() {
return name;
}
}

//自定义类
class Class1 implements Cloneable{
private Integer data;
private Class2 next;

@Override
public Object clone() {
Class1 cloned = null;
try {
cloned = (Class1) super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return cloned;
}
// 自定义方法
public void setData(Integer value) {
this.data = value;
}

public void setNext(Class2 myclass) {
this.next = myclass;
}

public Class2 getNext() {
return (Class2) this.next;
}
public void display() {
System.out.println("data = " + data);
System.out.println("next address = " + next);
System.out.println("next value = " + next.getName());
}

public Class1 deepClone() {
Class1 cloned = null;
try {
cloned = (Class1)super.clone();
cloned.next = (Class2)next.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return cloned;
}
}
// 自定义类
class Class4 implements Serializable{
private String name;

// setter 和 getter 方法
public void setName(String str) {
name = str;
}

public String getName() {
return name;
}
}

class Class3 implements Serializable{
private Integer data;
private Class4 next;

public Class3 deepClone() {
Class3 cloned = null;
try {
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
cloned = (Class3)ois.readObject();
}catch(IOException e) {
e.printStackTrace();
}catch(ClassNotFoundException e) {
e.printStackTrace();
}
return cloned;
}

// 自定义方法 setter和getter
public void setData(Integer value) {
this.data = value;
}

public void setNext(Class4 myclass) {
this.next = myclass;
}

public Class4 getNext() {
return (Class4) this.next;
}

public void display() {
System.out.println("data = " + data);
System.out.println("next address = " + next);
System.out.println("next value = " + next.getName());
}
}

public class CopyMethod {

public static void test1(Class1 origin, Class1 cloned) {
System.out.println("Origin: ");
origin.display(); // 1, address1
System.out.println("Cloned: ");
cloned.display(); // 1, address1

System.out.println("--- Cloneable after change --- ");

cloned.setData(2); // set clone.data = 2
cloned.getNext().setName("XiaoMing"); // set clone.next.name = XiaoMing
System.out.println("Origin: ");
origin.display(); // 1, address1, origin.next.name = XiaoMing 可见next.data已经被修改了
System.out.println("Cloned: ");
cloned.display(); // 2, address1, cloned.next.name = XiaoMing
}

public static void test2(Class3 origin, Class3 cloned) {
System.out.println("Origin: ");
origin.display(); // 1, address1
System.out.println("Cloned: ");
cloned.display(); // 1, address1

System.out.println("--- after change --- ");

cloned.setData(2); // set clone.data = 2
cloned.getNext().setName("XiaoMing"); // set clone.next.name = XiaoMing
System.out.println("Origin: ");
origin.display(); // 1, address1, origin.next.name = XiaoMing 可见next.data已经被修改了
System.out.println("Cloned: ");
cloned.display(); // 2, address1, cloned.next.name = XiaoMing
}

public static void main(String[] args) {
Class1 origin = new Class1(); // origin
origin.setData(1); // set origin.data = 1
Class2 next = new Class2(); // next
next.setName("LiHua");
origin.setNext(next); // origin.next.name = LiHua

Class1 shallowCloned = (Class1) origin.clone(); // 浅拷贝对象
System.out.println("\n===== Shallow copy Test =====\n");
test1(origin, shallowCloned);

origin.getNext().setName("LiHua"); // 还原origin的值

Class1 deepCloned = (Class1) origin.deepClone();
System.out.println("\n=====Cloneable Deep copy Test =====\n");
test1(origin, deepCloned);

Class3 origin2 = new Class3();
origin2.setData(1);
Class4 next2 = new Class4(); // next
next2.setName("LiHua");
origin2.setNext(next2); // origin.next.name = LiHua
Class3 deepCloned2 = (Class3)origin2.deepClone();
System.out.println("\n=====Serializable Deep copy Test =====\n");
test2(origin2, deepCloned2);

}
}