提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
这里用了两个定义了类 为了方便下面的举例说明:
Major(专业)类
public class Major {
private String majorName;
private long majorId;
public Major(String majorName, long majorId) {
this.majorName = majorName;
this.majorId = majorId;
}
public String getMajorName() {
return majorName;
}
public void setMajorName(String majorName) {
this.majorName = majorName;
}
public long getMajorId() {
return majorId;
}
public void setMajorId(long majorId) {
this.majorId = majorId;
}
@Override
public String toString() {
return "Major{" +
"majorName='" + majorName + '\'' +
", majorId=" + majorId +
'}';
}
}
Student(学生)类
public class Student {
private String name;
private int age;
private Major major;
public Student(String name, int age, Major major) {
this.name = name;
this.age = age;
this.major = major;
}
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;
}
public Major getMajor() {
return major;
}
public void setMajor(Major major) {
this.major = major;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", major=" + major +
'}';
}
}
一、浅拷贝
浅拷贝属于对象克隆方式的一种,重要的特性体现在这个 「浅」 字上。只是增加了一个指针指向已存在的内存地址。
比如我们试图通过studen1实例,拷贝得到student2,如果是浅拷贝这种方式,大致模型可以示意成如下所示的样子:
很明显浅拷贝再拷贝基本类型的时候会把基本类型的数值复制一份再拷贝过去,而在拷贝引用类型的时候并不会单独复制一份拷贝过去,而是都指向一个引用类型。
浅拷贝代码实现
浅拷贝的典型实现方式是:让被复制对象的类实现Cloneable接口,并重写clone()方法即可。
public class Student implements Cloneable{
private String name;
private int age;
private Major major;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
然后我们来做个测试看看效果如何
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Major m = new Major("计算机科学与技术",666666);
Student student1 = new Student( "CodeSheep", 18, m );
// 由 student1 拷贝得到 student2
Student student2 = (Student) student1.clone();
//student1是否是同一个对象
System.out.println( student1 == student2 );
System.out.println( student1 );
System.out.println( student2 );
System.out.println();
System.out.println("修改之后:");
// 修改student1的值类型字段
student1.setAge( 35 );
// 修改student1的引用类型字段
m.setMajorName( "电子信息工程" );
m.setMajorId( 888888 );
System.out.println( student1 );
System.out.println( student2 );
}
}
结果:
D:\Java\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar=57104:D:\IDEA\IntelliJ IDEA 2019.3.3\bin" -Dfile.encoding=GBK -classpath D:\Java\jre\lib\charsets.jar;D:\Java\jre\lib\deploy.jar;D:\Java\jre\lib\ext\access-bridge-64.jar;D:\Java\jre\lib\ext\cldrdata.jar;D:\Java\jre\lib\ext\dnsns.jar;D:\Java\jre\lib\ext\jaccess.jar;D:\Java\jre\lib\ext\jfxrt.jar;D:\Java\jre\lib\ext\localedata.jar;D:\Java\jre\lib\ext\nashorn.jar;D:\Java\jre\lib\ext\sunec.jar;D:\Java\jre\lib\ext\sunjce_provider.jar;D:\Java\jre\lib\ext\sunmscapi.jar;D:\Java\jre\lib\ext\sunpkcs11.jar;D:\Java\jre\lib\ext\zipfs.jar;D:\Java\jre\lib\javaws.jar;D:\Java\jre\lib\jce.jar;D:\Java\jre\lib\jfr.jar;D:\Java\jre\lib\jfxswt.jar;D:\Java\jre\lib\jsse.jar;D:\Java\jre\lib\management-agent.jar;D:\Java\jre\lib\plugin.jar;D:\Java\jre\lib\resources.jar;D:\Java\jre\lib\rt.jar;G:\java\IDEA代码\Test1\out\production\Test1;D:\IDEA\dom4j-1.6.1.jar;D:\IDEA\lib\morph-1.1.1.jar;D:\IDEA\lib\ezmorph-1.0.6.jar;D:\IDEA\lib\commons-lang-2.5.jar;D:\IDEA\lib\json-lib-2.4-jdk15.jar;D:\IDEA\lib\morph-sandbox-1.1.1.jar;D:\IDEA\lib\commons-logging-1.1.1.jar;D:\IDEA\lib\commons-beanutils-1.8.3.jar;D:\IDEA\lib\commons-collections-3.2.1.jar;D:\IDEA\gson-2.2.4.jar;D:\IDEA\mysql-connector-java-5.1.6-bin.jar Practice.CSDN.Test
false
Student{name='CodeSheep', age=18, major=Major{majorName='计算机科学与技术', majorId=666666}}
Student{name='CodeSheep', age=18, major=Major{majorName='计算机科学与技术', majorId=666666}}
修改之后:
Student{name='CodeSheep', age=35, major=Major{majorName='电子信息工程', majorId=888888}}
Student{name='CodeSheep', age=18, major=Major{majorName='电子信息工程', majorId=888888}}
Process finished with exit code 0
可以很明显的看出当我们修改基本数据类型的时候另一个没有发生变化,而当我们修改引用类型的数据的时候另一个发生了变化,这就说明了他们指向的引用类型是同一个。
二、深拷贝
深拷贝相较于上面所示的浅拷贝,除了值类型字段会复制一份,引用类型字段所指向的对象,会在内存中也创建一个副本,
增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,就像这个样子:
可以看出深拷贝在浅拷贝的基础上,对引用类型的拷贝做出了变化,会把引用类型的数据再拷贝一份过去。
深拷贝的代码实现
虽然clone()方法可以完成对象的拷贝工作,但是注意:clone()方法默认是浅拷贝行为,就像上面的例子一样。若想实现深拷贝需覆写 clone()方法实现引用对象的深度遍历式拷贝,进行地毯式搜索。
所以对于上面的例子,如果想实现深拷贝,首先需要对更深一层次的引用类Major做改造,让其也实现Cloneable接口并重写clone()方法:
public class Major implements Cloneable{
private String majorName;
private long majorId;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
其次我们还需要在顶层的调用类中重写clone方法,来调用引用类型字段的clone()方法实现深度拷贝:
public class Student implements Cloneable{
private String name;
private int age;
private Major major;
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.major = (Major) major.clone(); // 重要!!!
return student;
}
再调用同一个测试用例:
false
Student{name='CodeSheep', age=18, major=Major{majorName='计算机科学与技术', majorId=666666}}
Student{name='CodeSheep', age=18, major=Major{majorName='计算机科学与技术', majorId=666666}}
修改之后:
Student{name='CodeSheep', age=35, major=Major{majorName='电子信息工程', majorId=888888}}
Student{name='CodeSheep', age=18, major=Major{majorName='计算机科学与技术', majorId=666666}}
Process finished with exit code 0
可以很明显的发现修改引用类型的数据的时候另一个没有跟随发生变化。说明是有两个引用类型的拷贝。