引入
在开发中我们常常遇到需要拷贝一个对象的情况,比如我所给出的例子;
现在我们有一个Student对象的模板,该对象包含姓名、性别和学校三个基本属性,其中学校属性已赋值,现要基于这个Stu对象模板进行一些额外的赋值操作分别得到Stu1和Stu2,接下来看操作
Student对象
/**
* @author CaveWang
* @date 2022/10/24 22:41
*/
public class Student {
private String NAME;
private String SEX;
private String SCHOOL;
public String getNAME() {
return NAME;
}
public void setNAME(String NAME) {
this.NAME = NAME;
}
public String getSEX() {
return SEX;
}
public void setSEX(String SEX) {
this.SEX = SEX;
}
public String getSCHOOL() {
return SCHOOL;
}
public void setSCHOOL(String SCHOOL) {
this.SCHOOL = SCHOOL;
}
@Override
public String toString() {
return "Student{" +
"NAME='" + NAME + '\'' +
", SEX='" + SEX + '\'' +
", SCHOOL='" + SCHOOL + '\'' +
'}';
}
}
测试
/**
* @author CaveWang
* @date 2022/10/24 22:42
*/
public class CloneTest {
public static void main(String[] args) {
//一个学生对象模板
Student student = new Student();
student.setSCHOOL("瓦罗兰大陆第三高级中学");
//基于学生模板进行修改得到stu1和stu2
Student student1 = CloneTest.method1(student);
Student student2 = CloneTest.method2(student);
System.out.println("student"+student);
System.out.println("student1"+student1);
System.out.println("student2"+student2);
}
public static Student method1(Student student){
student.setNAME("MoMo");
student.setSEX("female");
return student;
}
public static Student method2(Student student){
student.setNAME("Cave");
student.setSEX("male");
return student;
}
}
不出意外的话就要出意外了,或许许多基础不牢固的同学都会以为输出是这样的
错误的输出如下所示!
studentStudent{NAME=null, SEX=null, SCHOOL='瓦罗兰大陆第三高级中学'}
student1Student{NAME='MoMo', SEX='female', SCHOOL='瓦罗兰大陆第三高级中学'}
student2Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}
正确的输出如下所示!
studentStudent{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}
student1Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}
student2Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}
何解?在于我们只对原对象进行了赋值操作,当创建Stu对象时,它在堆内存中开辟了一块空间存储对象的内容。而当Stu1和Stu2通过method赋值时,Stu1和Stu2并不会再重新开辟一块堆内存,而是将Stu内存空间存储的对象的地址给到了Stu1和Stu2,这个地址存在栈内存中,通过栈内存的地址找到堆内存里对象的内容,Stu、Stu1、Stu2共用就完事了。所以说, 三个对象指向的都是同一块内容,不管谁改了对象的内容,另外两个再访问都是改过之后的了。
解决
这里提供三种解决方案
Get/Set
最简单粗暴的方式,重新定义一个对象进行Get和Set操作
Student student1 = new Student();
student1.setSCHOOL(student.getSCHOOL());
student1.setNAME("MoMo");
student1.setSEX("female");
Student student2 = new Student();
student2.setSCHOOL(student.getSCHOOL());
student2.setNAME("Cave");
student2.setSEX("male");
System.out.println("student"+student); //studentStudent{NAME='null', SEX='null', SCHOOL='瓦罗兰大陆第三高级中学'}
System.out.println("student1"+student1);//student1Student{NAME='MoMo', SEX='female', SCHOOL='瓦罗兰大陆第三高级中学'}
System.out.println("student2"+student2);//student2Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}
浅拷贝
我自认为浅拷贝是指拷贝时只拷贝对象本身,包括对象中的基本变量,比如Stu类中定义的姓名性别和学校名,而不拷贝对象包含的引用所指向的对象。
修改下Stu类,实现克隆接口,继承克隆方法
/**
* @author CaveWang
* @date 2022/10/24 22:41
*/
public class Student implements Cloneable{
private String NAME;
private String SEX;
private String SCHOOL;
public String getNAME() {
return NAME;
}
public void setNAME(String NAME) {
this.NAME = NAME;
}
public String getSEX() {
return SEX;
}
public void setSEX(String SEX) {
this.SEX = SEX;
}
public String getSCHOOL() {
return SCHOOL;
}
public void setSCHOOL(String SCHOOL) {
this.SCHOOL = SCHOOL;
}
@Override
public String toString() {
return "Student{" +
"NAME='" + NAME + '\'' +
", SEX='" + SEX + '\'' +
", SCHOOL='" + SCHOOL + '\'' +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
然后克隆对象再输出,发现基本变量是没有问题的。
Student student = new Student();
student.setSCHOOL("瓦罗兰大陆第三高级中学");
Student student1 = (Student) student.clone();
student1.setNAME("MoMo");
student1.setSEX("female");
Student student2 = (Student) student.clone();
student2.setNAME("Cave");
student2.setSEX("male");
System.out.println("student"+student);
System.out.println("student1"+student1);
System.out.println("student2"+student2);
如果此时在Stu中再加入一个引用类型呢?
创建Subject课程类,其中有课程名和分数两个字段
/**
* @author CaveWang
* @date 2022/10/25 23:14
*/
public class Subjects {
private String name;
private String score;
public Subjects(String name, String score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
@Override
public String toString() {
return "Subjects{" +
"name='" + name + '\'' +
", score='" + score + '\'' +
'}';
}
}
然后在Stu类中添加课程字段,该字段为引用类型
public class Student implements Cloneable{
private String NAME;
private String SEX;
private String SCHOOL;
private Subjects subjects;
public Student(String NAME, String SEX, String SCHOOL, Subjects subjects) {
this.NAME = NAME;
this.SEX = SEX;
this.SCHOOL = SCHOOL;
this.subjects = subjects;
}
public void setSubjectsScore(String score) {
this.subjects.setScore(score);
}
public void setSubjectsName(String name) {
this.subjects.setName(name);
}
public String getNAME() {
return NAME;
}
public void setNAME(String NAME) {
this.NAME = NAME;
}
public String getSEX() {
return SEX;
}
public void setSEX(String SEX) {
this.SEX = SEX;
}
public String getSCHOOL() {
return SCHOOL;
}
public void setSCHOOL(String SCHOOL) {
this.SCHOOL = SCHOOL;
}
@Override
public String toString() {
return "Student{" +
"NAME='" + NAME + '\'' +
", SEX='" + SEX + '\'' +
", SCHOOL='" + SCHOOL + '\'' +
", subjects=" + subjects +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试类:
/**
* @author CaveWang
* @date 2022/10/24 22:42
*/
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
//一个学生对象模板
Student student = new Student(null,null,"瓦罗兰大陆第三高级中学",new Subjects("数据结构","80"));
Student student1 = (Student) student.clone();
student1.setNAME("MoMo");
student1.setSEX("female");
//修改课程名和分数
student1.setSubjectsName("数字图像处理");
student1.setSubjectsScore("90");
Student student2 = (Student) student.clone();
student2.setNAME("Cave");
student2.setSEX("male");
//修改课程名和分数
student2.setSubjectsName("二维游戏开发");
student2.setSubjectsScore("95");
System.out.println("student="+student);
System.out.println("student1="+student1);
System.out.println("student2="+student2);
}
public static Student method1(Student student){
student.setNAME("MoMo");
student.setSEX("female");
return student;
}
public static Student method2(Student student){
student.setNAME("Cave");
student.setSEX("male");
return student;
}
}
输出:
student=Student{NAME='null', SEX='null', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='二维游戏开发', score='95'}}
student1=Student{NAME='MoMo', SEX='female', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='二维游戏开发', score='95'}}
student2=Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='二维游戏开发', score='95'}}
从最后对studnet的输出可以看到,student、studnet1 和 studnet2 的 subject 对象,实际上还是指向了统一个对象,只对对它的引用进行了传递。
深拷贝
实现深拷贝常见有两种方式,序列化和重写clone()方法,本文主要介绍clone方法。
继续改写上述代码,让subject类也继承Cloneable接口
public class Subjects implements Cloneable{
private String name;
private String score;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
....
}
然后最重要的部分来了,在student的clone()方法中也将subject进行克隆
public class Student implements Cloneable{
private String NAME;
private String SEX;
private String SCHOOL;
private Subjects subjects;
@Override
public Object clone() throws CloneNotSupportedException {
Student student = (Student)super.clone();
student.subjects = (Subjects) this.subjects.clone();
return student;
}
...
}
此时再看输出,大功告成!
student=Student{NAME='null', SEX='null', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='数据结构', score='80'}}
student1=Student{NAME='MoMo', SEX='female', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='数字图像处理', score='90'}}
student2=Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='二维游戏开发', score='95'}}