一. 前置理解:
1.首先你需要了解基本数据类型和引用数据类型,值传递和引用传递,这里就不展开赘述了,不了解的可以自行递归。
2.以下面公共代码为例的结构图是这样的
image.png
敲黑板!!!! 一定要学好基础知识,否则当你打开一篇文字你会接着打开更多的文章!!!
二. 如何实现拷贝
首先要实现Cloneable接口,并重写clone()方法,提升可见性,从Object类的clone()方法的访问修饰符我们可以看到他为protected,重写后可将其提升为public
三. 公共代码
1.Student类
public class Student {
private String name;
private int age;
private Achievement achievement;
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 Student(String name, int age) {
this.name = name;
this.age = age;
}
public Achievement getAchievement() {
return achievement;
}
public void setAchievement(Achievement achievement) {
this.achievement = achievement;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", achievement=" + achievement.toString() +
'}';
}
}
2. 成绩类
public class Achievement {
private String name;
private int sore;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSore() {
return sore;
}
public void setSore(int sore) {
this.sore = sore;
}
public Achievement(String name, int sore) {
this.name = name;
this.sore = sore;
}
@Override
public String toString() {
return "Achievement{" +
"name='" + name + '\'' +
", sore=" + sore +
'}';
}
}
四. 直接传递
//实例化一个studentA属性
Student studentA = new Student("张三",12);
//实例化一个子属性(实际就是student类对象的achievement属性)
Achievement achievement = new Achievement("数学",100);
//将子属性添加到父属性studentA中
studentA.setAchievement(achievement);
//直接将父属性studentA赋值给studentB
Student studentB = studentA;
System.out.println("修改前为studentA:"+studentA.toString());
System.out.println("修改前为studentB:"+studentA.toString());
//修改studentB的其中一个父属性
studentB.setName("李四");
//修改studentB的子属性的其中一个属性
studentB.getAchievement().setName("语文");
System.out.println("修改后studentA为:"+studentA.toString());
System.out.println("修改后studentB为:"+studentB.toString());
输出结果:
修改前为studentA:Student{name='张三', age=12, achievement=Achievement{name='数学', sore=100}}
修改前为studentB:Student{name='张三', age=12, achievement=Achievement{name='数学', sore=100}}
修改后studentA为:Student{name='李四', age=12, achievement=Achievement{name='语文', sore=100}}
修改后studentB为:Student{name='李四', age=12, achievement=Achievement{name='语文', sore=100}}
为啥?
studentA和studentB都是对象类型,在对象类型数据传递时为引用传递,即studentA和studentB都指向了同一个数据存放地址,当我修改了任一对象的属性,也就意味着studentA和studentB所指向的内存地址中的数据被改变了,所以修改后studentA和studentB输出的内容完全一样的。
五. 浅拷贝
1.student类改动
public class Student implements Cloneable {
private String name;
private int age;
private Achievement achievement;
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 Student(String name, int age) {
this.name = name;
this.age = age;
}
public Achievement getAchievement() {
return achievement;
}
public void setAchievement(Achievement achievement) {
this.achievement = achievement;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", achievement=" + achievement.toString() +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
入口函数
try{
//实例化对象A
Student studentA = new Student("李四",11);
//实例化一个子属性(实际就是student类对象的achievement属性)
Achievement achievement = new Achievement("数学",100);
//将子属性添加到父属性studentA中
studentA.setAchievement(achievement);
//将studentA拷贝到studentB中,由于调用的是Object的clone方法,则返回数据为Object类型,需要强制转型为Student类类型
Student studentB = (Student) studentA.clone();
System.out.println("修改前为studentA:"+studentA.toString());
System.out.println("修改前为studentB:"+studentB.toString());
//修改studentB的父属性
studentB.setName("王五");
studentB.setAge(12);
//修改studentB的子属性
studentB.getAchievement().setName("语文");
studentB.getAchievement().setScore(90);
System.out.println("修改后为studentA:"+studentA.toString());
System.out.println("修改后为studentB:"+studentB.toString());
System.out.println("studentA父属性name的内存地址是:"+studentA.hashCode());
System.out.println("studentB父属性name的内存地址是:"+studentB.hashCode());
System.out.println("studentA父属性name的内存地址是:"+studentA.getAchievement().getName().hashCode());
System.out.println("studentB父属性name的内存地址是:"+studentB.getAchievement().getName().hashCode());
}catch (Exception e){
e.printStackTrace();
}
输出结果:
修改前为studentA:Student{name='李四', age=11, achievement=Achievement{name='数学', sore=100}}
修改前为studentB:Student{name='李四', age=11, achievement=Achievement{name='数学', sore=100}}
修改后为studentA:Student{name='李四', age=11, achievement=Achievement{name='语文', sore=90}}
修改后为studentB:Student{name='王五', age=12, achievement=Achievement{name='语文', sore=90}}
studentA父属性name的内存地址是:1625635731
studentB父属性name的内存地址是:1580066828
studentA父属性name的内存地址是:1136442
studentB父属性name的内存地址是:1136442
我们会发现父属性被替换了,因为当我们调用的Object类的clone方法后,为我们的name和age父属性开辟了新的空间,意味着studentA和B的父属性指向的已经不是同一个地址了,所以我随便改动任一都不会对彼此造成影响。hashcode方法是由内存地址运算后得来的,也就意味着与内存地址有息息相关,所以我们可以通过hashcode方法返回的值去判断内存地址是否一致
六. 深拷贝
1.Student类改动:
private String name;
private int age;
private Achievement achievement;
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 Student(String name, int age) {
this.name = name;
this.age = age;
}
public Achievement getAchievement() {
return achievement;
}
public void setAchievement(Achievement achievement) {
this.achievement = achievement;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", achievement=" + achievement.toString() +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
//首先创建一个新对象并将原对象的父属性copy过去
Student student = (Student) super.clone();
//接着我们在clone子属性并赋值到新的student对象中
student.setAchievement((Achievement) achievement.clone());
return student;
}
Achievement改动
private String name;
private int score;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public Achievement(String name, int sore) {
this.name = name;
this.score = sore;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Achievement{" +
"name='" + name + '\'' +
", sore=" + score +
'}';
}
与浅拷贝同样的测试代码运行后的结果:
修改后为studentA:Student{name='李四', age=11, achievement=Achievement{name='数学', sore=100}}
修改后为studentB:Student{name='王五', age=12, achievement=Achievement{name='语文', sore=90}}
studentA父属性name的内存地址是:1625635731
studentB父属性name的内存地址是:1580066828
studentA父属性name的内存地址是:828406
studentB父属性name的内存地址是:1136442
我们可以发现不管studentA和B的父属性和子属性已经不再指向同一个内存空间了
注意事项:
1.深拷贝和浅拷贝在代码层的实现区别在于深拷贝需要将对象中的所有层都要实现Cloneable接口,并且重写clone方法
2.所有的类都是Object类的子类,所以当我们重写clone时有可能会忘记实现Cloneable接口,重写clone方法就必须要实现Cloneable接口,否则会报java.lang.CloneNotSupportedException
如大神发现文中出现错误,请大家在评论区进行留言