深拷贝和浅拷贝

张天宇 on 2020-08-27

梳理一下深拷贝和浅拷贝。

引子

先定义两个环境类,这里就以 StudentMajor 为例,表示学生和学生所学专业,它们应该是包含关系。

1
2
3
4
5
6
7
8
9
class Student{
public String name;
public Major major;
// 省略 constructor, getter 和 setter,下同
}
class Major {
public String major;
public String majorid;
}

先看一种写法:

1
2
3
4
5
Major major = new Major("1", "2");
Student student = new Student("zty", major);
Student student2 = student;
System.out.println(student == student2);
// true

严格来讲,这其实并不是“拷贝”,只是给了 student2 一个引用关系,因为没有生成新的实际对象,验证:

1
2
3
student2.setName("galaxy");
System.out.println(student.getName());
// galaxy

可以看到修改 student2student 也被影响。

浅拷贝

浅,就是对于一个对象来说,值类型的字段会复制一份,但是引用类型的字段拷贝的仅仅是引用地址,引用的对象没有被拷贝,只有一份。

浅拷贝

实现方式

让被复制对象的类实现 Cloneable 接口,并重写 clone() 方法。

1
2
3
4
5
6
7
8
class Student implements Cloneable {
public String name;
public Major major;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
1
2
3
4
5
6
7
8
9
10
11
Major major = new Major("1", "2");
Student student = new Student("zty", major);
Student student3 = (Student) student.clone();
System.out.println(student == student3);
student3.setName("galaxy");
student3.major.setMajor("fdsfds");
System.out.println(student.getName() + " " + student3.getName());
System.out.println(student.getMajor() == student3.getMajor());
// false
// zty galaxy
// true

可以看到克隆了一个新对象,且值类型不再于原对象关联,但是引用类型仍然是一个。

深拷贝

深,就是除了值类型,引用也会复制一个副本出来。clone() 默认是浅拷贝,要想实现深拷贝,就要重写,进行深度遍历复制。

深拷贝

实现方式

先对 Major 类进行改造,这里 Major 里面没东西了,直接浅拷贝:

1
2
3
4
5
6
7
8
class Major implements Cloneable {
public String major;
public String majorid;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

Student 中重写 clone 方法,实现深拷贝:

1
2
3
4
5
6
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.setMajor((Major) major.clone());
return student;
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Student student3 = (Student) student.clone();
System.out.println(student == student3);
student3.setName("galaxy");
student3.major.setMajor("fdsfds");
System.out.println(student.getName() + " " + student3.getName());
System.out.println(student.getMajor() == student3.getMajor());
student3.getMajor().setMajor("xiugai");
System.out.println(student.getMajor().getMajor() + " " + student3.getMajor().getMajor());
/**
false
zty galaxy
false
1 xiugai
**/

序列化实现深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Student implements Serializable {
public String name;
public Major major;
@Override
public Student clone() {
try {
// 将对象序列化到字节流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
// 反序列化回对象
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (Student) objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}

注意,此种方法,Major 类也要实现序列化。