文章图片
大家好,我是小鱼儿目录
引子
克隆方法的第一次尝试
浅拷贝的实现
深拷贝的实现
新的一天,让我们一起学习,一起冲冲冲
引子下面由一个栗子引出我们今天的话题
class Student {
public long id = 1024;
@Override // 重写父类Object的toString方法,以便在主函数里println()可以直接打印出对象的各个属性,原因我们上篇博客已经讲过了
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
public class test3 {
public static void main(String[] args) {
Student student1 = new Student();
System.out.println(student1);
// 因为我们重写了父类Object的toString方法,所以可以直接打印出对象的值来
}
}
如图我们自定义了一个学生类并且实例化了一个对象student1,在内存中就是这样:
文章图片
那么如果我们想在堆内存中对student1对象拷贝一份怎么办呢???
可能有同学会直接写下这样的代码
Student student2 = student1;
但这只是我们在栈上重新定义了一个引用变量student2,并指向了堆上的student1对象,并没有对我们的student1实现拷贝
文章图片
不信的话,你可以试试,看看当我们改变student2中的id属性时,student1的属性会不会变
class Student {
public long id = 1024;
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
public class test3 {
public static void main(String[] args) {
Student student1 = new Student();
Student student2 = student1;
System.out.print("改变前的:");
System.out.print(student1);
System.out.println(student2);
student2.id = 777;
System.out.print("改变后的:");
System.out.print(student1);
System.out.println(student2);
}
}
文章图片
克隆方法的第一次尝试 那么我们该怎样拷贝students1这个对象呢?我们可以运用克隆方法clone()来进行拷贝
文章图片
你会发现clone标红报错了,为啥呢?首先只有一个对象能够被克隆的时候,他才能调用clone。而怎样的对象才能够被克隆呢?
那就是该对象所对应的类必须实现Cloneable这个接口才能够被克隆。那行,我们实现Cloneable接口后再调用clone方法试试。
文章图片
文章图片
别着急,我们先看看Cloneable这个接口的源码再说
文章图片
那Clone存在的意义是什么呀?——》他是一个标记接口,说明了该类的对象是可以被克隆的。
那好吧!我们怎样才能用上clone方法呢,我们需要在子类Student中重写父类Object的clone方法。至于为啥要重写而不能直接调用父类的clone方法,这个以我们现在先不用深究,先记住就行
那好我们就重写父类Object的clone方法,至于怎么重写:在IDEA里就有相应的快捷键来直接生成我们的重写方法——》ctrl+O
文章图片
文章图片
在内存中就是这样:
文章图片
来看看此时的代码
文章图片
这时你会发现你的student1可以student1.clone了,那么这就好了吗?
文章图片
文章图片
这时我们再按下回车键,编译器自动就给我们处理了这个异常
文章图片
好了,经过一番挫折,我们终于成功的把student1这个对象拷贝了一份。总结一下就是:
- 要克隆的这个对象的类必须实现 Cloneable 接口
- 类中重写 Object 的 clone() 方法
- 处理重写clone方法时的异常情况
- clone方法需要进行强转(比较特殊,先记住就好)
浅拷贝的实现 但如果我们在Student类中再定义一个引用类型呢?
文章图片
此时在内存中就变成了这样:
文章图片
我们可以用代码验证一下:
class Money {
int money = 17;
}
class Student extends test3 implements Cloneable{
public long id = 1024;
Money m = new Money();
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class test3 {
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student();
Student student2 = (Student) student1.clone();
System.out.print("改变前的:");
System.out.print(student1.m.money + "");
System.out.println(student2.m.money);
student1.m.money = 98;
System.out.print("改变后的:");
System.out.print(student1.m.money + "");
System.out.println(student2.m.money);
}
}
文章图片
这种就称作浅拷贝 ,然后我们就可以引出浅拷贝的概念了:
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝的实现 那怎样把我们的Money类对象也在堆上上拷贝一份呢?
我们刚才通过实现Comparable接口、重写clone方法对Student类实现了拷贝,那么同理我们也可以用这样的办法对Money类对象进行拷贝,嘻嘻!于是代码就变成了这样
class Money implements Cloneable{// 对Money类实现Cloneable接口
int money = 17;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
// 重写clone方法
}
}
class Student extends test3 implements Cloneable{
public long id = 1024;
Money m = new Money();
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}@Override
protected Object clone() throws CloneNotSupportedException {
// 此时我们在进行 “(Student) student1.clone();
” 操作,我们在堆上对student1克隆拷贝出来一个新对象,并让引用变量tmp指向新对象
// 之前我们是return super.clone(),然后再student1.clone()克隆student1对象,这和直接super.clone是一样的,都是重写了父类Object的clone方法后进行克隆
Student tmp = (Student) super.clone();
// 这里的this就是我们要克隆的student1对象,他的一个引用变量m也指向了一个Money类对象,
// 我们刚才对Money类进行了处理所以可以用this.m.clone对引用变量m所指向的Money类对象进行克隆
tmp.m = (Money) this.m.clone();
// 把把克隆出来的tmp中的money也给克隆一份,因为tmp.m是Money类型的所以要强制类型转换//以前只是把把student1对象的各个值克隆一份,引用变量m所对应的Money类对象并没有克隆,现在是把已经克隆好 Money类对象 的 tmp来返回
return tmp;
}
}
public class test3 {
public static void main(String[] args) throws CloneNotSupportedException { // 处理克隆方法clone的异常
Student student1 = new Student();
Student student2 = (Student) student1.clone();
// 此时的student.clone返回Student类对象的引用tmp
// 这样的话,student2 就指向了原来tmp所指向的对象
System.out.print("改变前的:");
System.out.print(student1.m.money + "");
System.out.println(student2.m.money);
student1.m.money = 98;
System.out.print("改变后的:");
System.out.print(student1.m.money + "");
System.out.println(student2.m.money);
}
}
在这个代码中有几个关键点需要解释一下:
文章图片
第一步分析:
文章图片
第二步分析
文章图片
第三步分析
文章图片
第四步分析
文章图片
上面的拷贝就把引用变量m所指向的Money类的对象也在堆中拷贝了一份,这就是深拷贝,
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
总结一下就是
浅拷贝:创建一个新对象,然后将当前对象的各种成员属性复制到该新对象,如果成员属性是值类型的,那么对该属性执行复制;如果该字段是引用类型的话,则只是复制引用但不复制引用所指向的对象。
深拷贝:创建一个新对象,然后将当前对象的各种成员属性复制到该新对象,无论该成员属性是值类型的还是引用类型,都复制独立的一份,引用类型也会复制该引用类型所指向的对象。
好了今天我们就学到这里,铁汁们咱们下篇聊聊Java中Comparable接口和Comparator 接口,看看这两个小家伙是怎么用的,嘻嘻
文章图片
【JavaSE|一篇文章带你彻底理解Java中的克隆和拷贝】
推荐阅读
- JavaSE|println输入和toString方法的重写
- java|Spring 事务没生效的几种可能性。 will not be managed by Spring
- spring|spring boot访问接口报500
- nginx|申请CA证书的步骤
- JAVA人生|外包干了五年,废了...
- Spring|Spring Boot开发简单网页(员工管理系统)(五)(登录功能实现)
- java|java spring上传图片_java基于spring boot本地上传图片示例解析
- 自动驾驶|自动驾驶 java_深入浅出自动驾驶(一)-图像识别
- 百度|《2022版大数据必备Linux命令》,高清完整版下载!