avatar

目录
Java中的深拷贝和浅拷贝

1 Java中对象的创建

首先引入一个问题,在java语言中,有几种方式可以创建对象呢?

  1. 使用new操作符创建一个对象
  2. 使用clone方法复制一个对象

那么这两种方式有什么相同和不同呢? new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。

2 引用拷贝和对象拷贝

引用拷贝:引用拷贝也就是我们常用的对象赋值,这种方式不会生成新的对象,只会在原对象上增加了一个新的对象引用,两个引用指向的对象还是是同一个。因为两个引用指向同一个对象所以,使用其中一个引用修改了对象的值,另一个引用所对应的值也会改变。

引用拷贝代码示例

Teacher类:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Teacher{
String name;

Teacher(String name){
this.name = name;
}

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

public String getName() {
return name;
}
}

主类:

java
1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher t1 = new Teacher("王老师");
Teacher t2 = t1;

// t1和t2是指向同一个地址的
System.out.println(t1); // Teacher@1b6d3586
System.out.println(t2); // Teacher@1b6d3586
}
}

对象拷贝:这种方式会重新生成一个新的对象,生成的新对象与原来的对象没有任何关联。对象拷贝包含浅拷贝和深拷贝。

3 浅拷贝和深拷贝

开发过程中,有时会遇到把现有的一个对象的所有成员属性拷贝给另一个对象的需求。

比如说对象 A 和对象 B,二者都是 ClassC 的对象,具有成员变量 a 和 b,现在对对象 A 进行拷贝赋值给 B,也就是 B.a = A.a; B.b = A.b;

这时再去改变 B 的属性 a 或者 b 时,可能会遇到问题:假设 a 是基础数据类型,b 是引用类型。

当改变 B.a 的值时,没有问题;

当改变 B.b 的值时,同时也会改变 A.b 的值,因为其实上面的例子中只是把 A.b 赋值给了 B.b,因为是 b 引用类型的,所以它们是指向同一个地址的。这可能就会给我们使用埋下隐患。

Java 中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。

上面的问题,其实就是因为对拷贝的不熟悉导致的。
根据对对象属性的拷贝程度(基本数据类和引用类型),会分为两种:浅拷贝 (Shallow Copy)和深拷贝 (Deep Copy)

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

浅拷贝的特点

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。

(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。

深拷贝

深拷贝在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。

深拷贝特点

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。

(2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。

(3) 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。

(4) 深拷贝相比于浅拷贝速度较慢并且花销较大。

4 浅拷贝代码示例

Teacher类:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Teacher implements Cloneable {
String name;

Teacher(String name){
this.name = name;
}

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

public String getName() {
return name;
}

public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

Student类:

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
public class Student implements Cloneable{
int id;
String name;
Teacher teacher;
Student(int id, String name, Teacher teacher){
this.id = id;
this.name = name;
this.teacher = teacher;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

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

public Teacher getTeacher() {
return teacher;
}

public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

主类:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {

// 浅拷贝
Student t1 = new Student(1,"小红",new Teacher("老师a"));
Student t2 = (Student)t1.clone();

System.out.println(t1); // Student@1b6d3586
System.out.println(t2); //Student@1b6d3586

t2.setId(2);
t2.setName("小张");
t2.getTeacher().setName("老师b"); // 会修改t1的值

System.out.println(t1.getId() + "," + t1.getName() + "," + t1.getTeacher().getName()); // 1,小红,老师b
System.out.println(t2.getId() + "," + t2.getName() + "," + t2.getTeacher().getName()); // 2,小张,老师b

}
}

5 深拷贝代码示例

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
public class Student implements Cloneable{
int id;
String name;
Teacher teacher;
Student(int id, String name, Teacher teacher){
this.id = id;
this.name = name;
this.teacher = teacher;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

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

public Teacher getTeacher() {
return teacher;
}

public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}

@Override
protected Object clone() throws CloneNotSupportedException {

//return super.clone();

// 修改为深拷贝
Student student = (Student)super.clone();
student.setTeacher((Teacher) student.getTeacher().clone());
return student;
}
}

深拷贝其他代码相同,只需要修改Student类中的clone()函数:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {

// 浅拷贝
Student t1 = new Student(1,"小红",new Teacher("老师a"));
Student t2 = (Student)t1.clone();

System.out.println(t1); // Student@1b6d3586
System.out.println(t2); //Student@1b6d3586

t2.setId(2);
t2.setName("小张");
t2.getTeacher().setName("老师b"); // 不会修改t1的值

System.out.println(t1.getId() + "," + t1.getName() + "," + t1.getTeacher().getName()); // 1,小红,老师a
System.out.println(t2.getId() + "," + t2.getName() + "," + t2.getTeacher().getName()); // 2,小张,老师b

}
}

参考:

文章作者: foochane
文章链接: https://foochane.cn/article/2020072001.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 foochane
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论