Java深克隆和浅克隆的原理及实现
参考:
https://www.jianshu.com/p/94dbef2de298
https://www.cnblogs.com/shakinghead/p/7651502.html
Java 中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。
根据对对象属性的拷贝程度(基本数据类和引用类型),会分为两种:
浅拷贝 (Shallow Copy)
深拷贝 (Deep Copy)
浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。
结构图如下:
浅拷贝的实现
实现对象拷贝的类,需要实现 Cloneable 接口,并覆写 clone() 方法
示例:
package com.lvyuanj.core.model;
import lombok.Data;
@Data
public class Teacher implements Cloneable {
private String name;
private int age;
public Teacher(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.lvyuanj.core.model;
import lombok.Data;
@Data
public class Student implements Cloneable{
private String name;
private int age;
private Teacher teacher;
public Student(String name,int age,Teacher teacher){
this.name = name;
this.age = age;
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", teacher=" + teacher +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher("王老师",30);
Student student = new Student("张三", 20, teacher);
System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));
System.out.println("teacher:"+teacher+ ",hashcode"+System.identityHashCode(teacher));
Student student1 = (Student) student.clone();
student1.setName("李四");
student1.setAge(40);
Teacher teacher1 = student1.getTeacher();
teacher1.setAge(90);
System.out.println("student1:"+student1 + ",hashcode:"+System.identityHashCode(student1));
System.out.println("teacher1 "+teacher1 + ",hashcode:"+System.identityHashCode(student1.getTeacher()));
System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));
}
}
运行结果:
student:Student{name='张三', age=20, teacher=Teacher{name='王老师', age=30}},hashcode1872034366
teacher:Teacher{name='王老师', age=30},hashcode1581781576
student1:Student{name='李四', age=40, teacher=Teacher{name='王老师', age=90}},hashcode:1725154839
teacher1 Teacher{name='王老师', age=90},hashcode:1581781576
student:Student{name='张三', age=20, teacher=Teacher{name='王老师', age=90}},hashcode1872034366
由输出的结果可见,通过 student.clone() 拷贝对象后得到的 student1,和 student 是两个不同的对象。student 和 student1 的基础数据类型的修改互不影响,而引用类型 Teacher 修改后是会有影响的。
student的基础数据类型:name = "张三" ,age = 20
student1的基础数据类型:name = "李四" ,age = 90
student1的引用类型teacher,修改age = 90 之后,student 中teacher.age =90 ;
可以通过hascode打印的值看的出来,引用类型的是相同内存地址,所以修改copy后中teacher.age,直接影响原来student中teacher对象的age
深拷贝
通过上面的例子可以看到,浅拷贝会带来数据安全方面的隐患,例如我们只是想修改了 student 的 teacher,但是 student1 的 teacher 也被修改了,因为它们都是指向的同一个地址。所以,此种情况下,我们需要用到深拷贝。
深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。
- 深拷贝特点
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
(2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
(3) 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
(4) 深拷贝相比于浅拷贝速度较慢并且花销较大。
3.深拷贝的实现方法主要有两种:
(1)、通过重写clone方法来实现深拷贝
(2)、通过对象序列化实现深拷贝
结构图如下:
一、通过重写clone方法来实现深拷贝
示例:
package com.lvyuanj.core.model;
import lombok.Data;
@Data
public class Teacher implements Cloneable {
private String name;
private int age;
public Teacher(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.lvyuanj.core.model;
import lombok.Data;
@Data
public class Student implements Cloneable{
private String name;
private int age;
private Teacher teacher;
public Student(String name,int age,Teacher teacher){
this.name = name;
this.age = age;
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", teacher=" + teacher +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.teacher = (Teacher) teacher.clone();
return student;
}
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher("王老师",30);
Student student = new Student("张三", 20, teacher);
System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));
System.out.println("teacher:"+teacher+ ",hashcode"+System.identityHashCode(teacher));
Student student1 = (Student) student.clone();
student1.setName("李四");
student1.setAge(40);
Teacher teacher1 = student1.getTeacher();
teacher1.setAge(90);
System.out.println("student1:"+student1 + ",hashcode:"+System.identityHashCode(student1));
System.out.println("teacher1 "+teacher1 + ",hashcode:"+System.identityHashCode(student1.getTeacher()));
System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));
}
}
运行结果:
student:Student{name='张三', age=20, teacher=Teacher{name='王老师', age=30}},hashcode1872034366
teacher:Teacher{name='王老师', age=30},hashcode1581781576
student1:Student{name='李四', age=40, teacher=Teacher{name='王老师', age=90}},hashcode:1725154839
teacher1 Teacher{name='王老师', age=90},hashcode:1670675563
student:Student{name='张三', age=20, teacher=Teacher{name='王老师', age=30}},hashcode1872034366
二、通过对象序列化实现深拷贝
虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。
示例:
package com.lvyuanj.core.model;
import lombok.Data;
import java.io.Serializable;
@Data
public class TeacherA implements Serializable {
private String name;
private int sex;
public TeacherA(String name,int sex){
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "TeacherA{" +
"name='" + name + '\'' +
", sex=" + sex +
'}';
}
}
package com.lvyuanj.core.model;
import lombok.Data;
import java.io.*;
@Data
public class StudentA implements Serializable {
private String name;
private int sex;
private TeacherA teacherA;
public StudentA(String name,int sex,TeacherA teacherA){
this.name = name;
this.sex = sex;
this.teacherA = teacherA;
}
@Override
public String toString() {
return "StudentA{" +
"name='" + name + '\'' +
", sex=" + sex +
", teacherA=" + teacherA +
'}';
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
TeacherA teacherA = new TeacherA("Arvin", 0);
StudentA studentA = new StudentA("Tom", 1, teacherA);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(studentA);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
StudentA copystu = (StudentA) ois.readObject();
System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));
System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
copystu.setName("copy-Arvin");
copystu.getTeacherA().setName("copy-tom");
System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));
System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
}
}
运行结果:
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1304836502
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1300109446
copystu:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:317574433
copystu teacher:TeacherA{name='Arvin', sex=0},hashCode:317574433
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1304836502
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1300109446
copystu:StudentA{name='copy-Arvin', sex=1, teacherA=TeacherA{name='copy-tom', sex=0}},hashCode:317574433
copystu teacher:TeacherA{name='copy-tom', sex=0},hashCode:317574433
可以通过很简洁的代码即可完美实现深拷贝。不过要注意的是,如果某个属性被transient修饰,那么该属性就无法被拷贝了。
示例:
package com.lvyuanj.core.model;
import lombok.Data;
import java.io.*;
@Data
public class StudentA implements Serializable {
private transient String name;
private int sex;
private TeacherA teacherA;
public StudentA(String name,int sex,TeacherA teacherA){
this.name = name;
this.sex = sex;
this.teacherA = teacherA;
}
@Override
public String toString() {
return "StudentA{" +
"name='" + name + '\'' +
", sex=" + sex +
", teacherA=" + teacherA +
'}';
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
TeacherA teacherA = new TeacherA("Arvin", 0);
StudentA studentA = new StudentA("Tom", 1, teacherA);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(studentA);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
StudentA copystu = (StudentA) ois.readObject();
System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));
System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
copystu.setName("copy-Arvin");
copystu.getTeacherA().setName("copy-tom");
System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));
System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
}
}
运行结果:
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:895328852
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1304836502
copystu:StudentA{name='null', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1854731462
copystu teacher:TeacherA{name='Arvin', sex=0},hashCode:1854731462
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:895328852
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1304836502
copystu:StudentA{name='copy-Arvin', sex=1, teacherA=TeacherA{name='copy-tom', sex=0}},hashCode:1854731462
copystu teacher:TeacherA{name='copy-tom', sex=0},hashCode:1854731462
以上代码中StudentA中name属性被transient修饰,copy之后name=null, 打印copystu:StudentA{name='null', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1854731462