Java 中的浅拷贝与深拷贝

很多时候,我们期望去拷贝某个对象的副本。在Java中如果使用 = 赋值操作,其实际上会让两个引用变量指向同一个对象;而如果重新new一个新对象,再对各字段进行赋值操作时,将十分繁琐。为此在Java的Object类中,提供了一个clone方法,其可拷贝对象副本

abstract.png

clone 方法 与 Cloneable 接口

Ojbect类中的clone方法签名如下,native 指示其是一个本地方法(非Java实现)。需要注意的是该方法的访问修饰符为 protected,说明该方法只能在本包下或子类中去调用

1
protected native Object clone() throws CloneNotSupportedException;

标记接口:没有任何方法和字段的空接口,其仅用于说明该类支持某种特性/功能。Cloneable 接口 即是一个标记接口,其只表明所标记类的对象是可拷贝的,否则对该类的对象调用 clone 方法将会抛 CloneNotSupportedException异常

下述代码示例即对Dept类对象进行拷贝操作,该类实现了Cloneable接口,故可正常调用clone方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data    
public class Dept implements Cloneable{
private String deptName;
private Integer totalNum;
private Integer deptIdx;

public static void inDeptTest1() throws CloneNotSupportedException {
Dept dept = new Dept();
dept.setDeptName("RD 2");
dept.setDeptIdx(22);
dept.setTotalNum(14);

Dept copy = (Dept) dept.clone(); // dept 的 clone 方法来自默认继承的Object类
System.out.println("dept == copy: " + (dept==copy) );
System.out.println("dept : " + dept);
System.out.println("copy : " + copy);
}
}

从下图测试结果可以看出,原对象dept 和 拷贝出的对象copy 地址不一样,其是两个独立的对象

figure 1.png

浅拷贝

Object类中的clone方法的访问修饰符为 protected,只能在包内及其子类调用。故在我们自己类中一般通过重载的方式将该类的访问权限改为 public 来方便我们在类外使用它。
如下代码所示,我们重载Dept的clone方法,也仅仅是扩大了访问权限,其依然是调用Object中的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
/**
* 部门类
*/
@Data
public class Dept implements Cloneable{
private String deptName; // 不可变对象属性
private Integer totalNum; // 不可变对象属性
private int deptIdx; // 非对象属性(基本类型属性)
private Date createTime; // 对象属性
private Fence fence; // 对象属性

@Override
public Object clone(){
Dept dept = null;
try{
dept = (Dept) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
return dept;
}
}

/**
* 地理围栏类
*/
@Data
public class Fence {
private Integer posX;
private Integer posY;
private Integer poxZ;
private String FenceName;
}

/**
* Dept类clone测试
*/
public class CloneTest {
public static void cloneDept() {
Dept dept = new Dept();
dept.setDeptName("RD 1");
dept.setTotalNum(11);
dept.setDeptIdx(1);
String dateStr = "2019-01-01 11:11:11";
dept.setCreateTime( DateUtil.parse(dateStr) );
Fence fence = new Fence();
fence.setFenceName("RD1 Fence");
fence.setPosX(1);
fence.setPosY(11);
fence.setPoxZ(111);
dept.setFence( fence );

Dept copy = (Dept) dept.clone();

System.out.println("----- Before Modify ---- ");
System.out.println( "dept: " + dept );
System.out.println( "copy: " + copy +"\r\n");

copy.setDeptName("RD 2");
copy.setTotalNum(22);
copy.setDeptIdx(2);
copy.getCreateTime().setHours(22);
copy.getFence().setFenceName("RD2 Fence");
copy.getFence().setPosX(2);
copy.getFence().setPosY(22);
copy.getFence().setPoxZ(222);

System.out.println("----- After Modify ---- ");
System.out.println( "dept: " + dept );
System.out.println( "copy: " + copy +"\r\n");
}
}

测试结果如下:

figure 2.jpeg

从上图可以看到可以看到对Dept类的副本对象copy进行修改后,发现原对象的部分字段也同时会发生变化。实际上,这种拷贝对象的非对象属性(基本类型属性)、不可变对象属性,但是不拷贝 对象的对象属性(不含不可变对象) ,即为浅拷贝。例如dept对象的对象属性fence即未被拷贝,副本对象copy的fence属性仍然指向原dept对象属性fence的对象。由于,dept对象的fence引用变量和副本对象copy的fence引用变量均指向同一Fence对象,所以当副本对象中的fence属性被修改时,可以看到原对象的fence同样会被修改受到影响。createTime属性同理。

Note:
对于不可变对象(String, 基本类型的包装器, BigInteger和BigDecimal等)而言,如上例所示的deptName属性,修改前,原对象dept和拷贝对象copy虽然都是指向同一个String对象——“RD 1”,但是由于String为不可变对象,是无法修改原String的状态的,其会生成一个新的String对象——“RD 2”,并使引用变量指向其。由于修改后,使得dept对象的deptName引用变量和副本对象copy的fence引用变量指向不同的String对象,所以修改副本对象的deptName属性不会对原对象的deptName属性造成任何影响

深拷贝

有了上文对浅拷贝的介绍,相信大家已经对其缺陷有了一定的认识,其对对象的拷贝程度非常“浅”,那么有没有办法完全完全的拷贝一个对象,使得其所有属性均和原对象完全不相干呢?答案就是深拷贝。其通过“层层clone”的方式实现对其需要对象中的所有对象属性进行克隆

以上文为例,我们首先对Fence类实现Cloneable接口重载clone方法,由于该类中除了不可变对象属性外无其他的对象属性,故只需调用Object类中的clone方法即可,无需“层层clone”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 地理围栏类
*/
@Data
public class Fence implements Cloneable {
private Integer posX;
private Integer posY;
private Integer poxZ;
private String FenceName;

@Override
public Object clone() {
Fence fence = null;
try {
fence = (Fence) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
return fence;
}
}

对于Dept类而言,其内部除了不可变对象属性外,有createTime和fence对象属性,所以需要进行“层层clone”,分别对这两个对象进行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
/**
* 部门类
*/
@Data
public class Dept implements Cloneable{
private String deptName;
private Integer totalNum;
private int deptIdx;
private Date createTime;
private Fence fence;

@Override
public Object clone(){
Dept dept = null;
try{
dept = (Dept) super.clone();
if( dept.getFence() != null ) {
Fence fence = (Fence) dept.getFence().clone();
dept.setFence(fence);
}
if( dept.getCreateTime() != null ) {
Date date = (Date) dept.getCreateTime().clone();
dept.setCreateTime(date);
}
} catch (CloneNotSupportedException e) {
return null;
}
return dept;
}
}

依旧调用之前的CloneTest.cloneDept方法进行测试,结果如下图所示,可以看到,通过深拷贝克隆出来的副本对象和原对象之前是完全无关系和影响的

figure 3.jpeg

0%