Java的深拷贝和浅拷贝
对象拷贝
在展开说深拷贝和浅拷贝之前,先来阐述阐述一下什么是对象拷贝。对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。
可以简单类比为在电脑上复制文件,这时候,复制普通文件和复制链接就产生了差异,这个差异就是接下来需要分析的深拷贝和浅拷贝的差异。
对象拷贝的实现
在Java中如果想要实现拷贝(忽略对象之间使用=
号),只能使用clone
方法。clone方法使用protect
修饰,声明在Object上,也就是所有Object子对象都可以使用clone方法进行对象拷贝。
1 | protected native Object clone() throws CloneNotSupportedException; |
在注释中可以看到,如果这个类没有继承自Cloneable
接口,那么它会抛出CloneNotSupportedException
异常。
在实现接口,并调用clone时,就能完成对象的拷贝。
深拷贝和浅拷贝
在对象拷贝章节类比电脑上复制文件一样,针对普通文件和链接文件有不同的处理方式,这种处理方式在Java对象拷贝的上的体现就是深拷贝。
在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。如果再拷贝对象的过程中,只对基本类型的变量进行了值得复制,却对引用类型只做了引用的复制(也就是内存地址引用),没有真正复制引用到的对象。此时的对象拷贝就叫做浅拷贝。
与之相反,不光对基本数据类型执行了值得复制,而且在复制引用类型复制时,不是仅仅传递引用,而是将引用到的对象真正的复制(分配内存),此时的对象拷贝就叫做深拷贝。
深拷贝和浅拷贝的区别
其实上面在引申概念时,就已经得出深拷贝和浅拷贝的区别了,深拷贝不光要拷贝进本数据类型的值,还要完成对引用类型创建一个新的对象,并复制其内的成员变量。
深拷贝和浅拷贝的实例
- 浅拷贝用例
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
52public class CloneTest implements Cloneable {
public int x;
public SonClone son;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public SonClone getSon() {
return son;
}
public void setSon(SonClone son) {
this.son = son;
}
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void testClone(){
CloneTest test = new CloneTest();
test.setX(127);
test.setSon(new SonClone());
try {
CloneTest clone = (CloneTest) test.clone();
// 比较test和复制对象copy
System.out.println("test == clone --> "
+(test == clone));
System.out.println("test.hash == clone.hash --> "
+(test.hashCode() == clone.hashCode()));
System.out.println("test.getClass() == clone.getClass() --> "
+ (test.getClass() == clone.getClass()));
System.out.println("test.son == clone.son --> "
+(test.getSon() == clone.getSon()));
System.out.println("test.son.hash == clone.son.hash --> "
+(test.getSon().hashCode() == clone.getSon().hashCode()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
class SonClone implements Cloneable{
int a;
}
}
浅拷贝的执行结果如下:
1 | test == clone --> false |
可以看到,使用clone可以复制对象,对象的hashcode已经不相同了,但是引用对象却没有执行复制对象的过程,返回的hashcode值仍然是相同的,也就是仅仅复制了引用。
- 深拷贝用例
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
67public class DeepClone implements Cloneable{ public int x; public SonClone son;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public SonClone getSon() {
return son;
}
public void setSon(SonClone son) {
this.son = son;
}
class SonClone implements Cloneable{
int name;
public int getName() {
return name;
}
public void setName(int name) {
this.name = name;
}
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
protected Object clone() throws CloneNotSupportedException {
DeepClone clone = (DeepClone) super.clone();
clone.son = (SonClone) this.son.clone();
return clone;
}
public void testClone(){
DeepClone test = new DeepClone();
test.setX(127);
test.setSon(new SonClone());
try {
DeepClone clone = (DeepClone) test.clone();
// 比较test和复制对象copy
System.out.println("test == clone --> "
+(test == clone));
System.out.println("test.hash == clone.hash --> "
+(test.hashCode() == clone.hashCode()));
System.out.println("test.getClass() == clone.getClass() --> "
+ (test.getClass() == clone.getClass()));
System.out.println("test.x == clone.x --> "
+(test.getX() == clone.getX()));
System.out.println("test.son == clone.son --> "
+(test.getSon() == clone.getSon()));
System.out.println("test.son.hash == clone.son.hash --> "
+(test.getSon().hashCode() == clone.getSon().hashCode()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
这里深度拷贝可以看出在父类使用clone时,会手动将clone出的父类中的引用指向复制clone出来的子类对象。这时对父类执行了深拷贝,但实则对子类进行了一次浅拷贝。结果显而易见,最后的引用类型值和hashcode都不相同。
总结
拷贝对象需要使用clone方法,并需要继承Cloneable接口,如果不手动重写clone方法,则默认会只能执行浅拷贝。
浅拷贝只会复制基本数据类型的值,而不会复制引用类型的对象,而深拷贝需要手动编写clone方法来达到既能复制基本数据值,又能够完成对引用类型的对象的复制。