开发过程中,有时会遇到把现有的一个对象的所有成员属性拷贝给另一个对象的需求。
比如说对象 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)
一.浅拷贝
简介
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
特点
- 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
- 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。
如图:
代码实现
实现拷贝需要让被复制对象的类实现Cloneable
接口,并重写clone()
方法即可。
示例如下
public class Subject {
private String name;
private int age;
public Subject(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Subject{" +"hashCode=" + this.hashCode() +", 姓名='" + name + '\'' + ", 年龄=" + age + '}';
}
}
public class Student implements Cloneable{
//引用数据
private Subject subject;
private String name;
//基本数据类型
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Student{" +"hashCode=" + this.hashCode()+", subject=" + subject + ", 姓名='" + name + '\'' +", 年龄=" + age +'}';
}
}
测试类
public class ShallowCopy {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject = new Subject("李",1);
Student student1 = new Student();
student1.setSubject(subject);
student1.setName("张");
student1.setAge(13);
// 由 student1 拷贝得到 student2
Student student2 = (Student) student1.clone();
student2.setName("钱");
student2.setAge(15);
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
}
}
运行结果:
由输出的结果可见,通过 student1.clone() 拷贝对象后得到的 student2,和 student1 是两个不同的对象。student1 和 student2 的基础数据类型的修改互不影响,而引用类型 subject 修改后是会有影响的。
二.深拷贝
简介
虽然clone()方法可以完成对象的拷贝工作,但是注意:clone()方法默认是浅拷贝行为,就像上面的例子一样。若想实现深拷贝需覆写 clone()方法实现引用对象的深度遍历式拷贝,进行地毯式搜索。
深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。
特点
- 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
- 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
- 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
- 深拷贝相比于浅拷贝速度较慢并且花销较大。
如图:
代码实现
对于 Student 的引用类型的成员变量 Subject ,需要实现 Cloneable 并重写 clone() 方法。
public class Subject implements Cloneable{
private String name;
private int age;
public Subject(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Subject{" +"hashCode=" + this.hashCode() +", 姓名='" + name + '\'' + ", 年龄=" + age + '}';
}
}
在 Student 的 clone() 方法中,需要拿到拷贝自己后产生的新的对象,然后对新的对象的引用类型再调用拷贝操作,实现对引用类型成员变量的深拷贝。
public class Student implements Cloneable{
//引用数据
private Subject subject;
private String name;
//基本数据类型
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.subject = (Subject) subject.clone();
return student;
}
@Override
public String toString() {
return "Student{" +"hashCode=" + this.hashCode()+", subject=" + subject + ", 姓名='" + name + '\'' +", 年龄=" + age +'}';
}
}
测试类
public class DeepCopy {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject = new Subject("李",1);
Student student1 = new Student();
student1.setSubject(subject);
student1.setName("张");
student1.setAge(13);
// 由 student1 拷贝得到 student2
Student student2 = (Student) student1.clone();
student2.setName("钱");
student2.setAge(15);
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
}
}
运行结果
深拷贝后,student1和student2两个对象就完全独立了,不受互相的干扰。