首先我们来思考一个问题:如何进行对象的拷贝?
Object 类中存在一个 clone
方法, 调用这个方法可以创建一个对象的 "拷贝"
. 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口
, 否则就会抛出 CloneNotSupportedException 异常.
我们先来看Clonable接口的源码:
我们可以看到这类接口什么都没有,那么我们称这类接口为
空接口
,也就是标记接口
.
空接口的意义
:标记当前这个类
是可以被克隆
的.克隆会做一件事情,拷贝一份副本.
同时关于对象的拷贝会牵扯出之前我们所提到的深拷贝和浅拷贝的问题
,下面我们来进行对比:
======================================================================================
首先一个对象的引用如果想要进行克隆,那么其对应的类必须实现Clonable 接口,并且重写Object类中的clone方法
,并且重写的clone方法后必须抛出CloneNotSupportedException 异常
.
我们直接通过代码来进行解析:先来看一段代码:
1.class Person implements Cloneable {
-
public int age;
-
@Override
-
//此处的clone方法必须抛出CloneNotSupportedException异常
-
protected Object clone() throws CloneNotSupportedException {
-
return super.clone();
-
}
8.}
9.//此处的主函数也需要抛出这个异常
-
public static void main(String[] args) throws CloneNotSupportedException {
-
Person person = new Person();
-
/*注意对于下面的调用clone方法有两点需要注意:
-
1:首先引用在调用clone方法时main方法需要抛出CloneNotSupportedException异常
-
2:因为clone方法的返回值为引用类型Object,所以如果是其他类定义的引用就需要强制类型转换
-
*/
-
Person person1 = (Person) person.clone();
-
System.out.println(person.age);
-
System.out.println(person1.age);
-
System.out.println("===========对比===========");
-
person1.age = 10;
-
System.out.println(person.age);
-
System.out.println(person1.age);
-
}
此段代码的输出结果为:
此时我们会发现首先我们对person所指向的对象进行了拷贝,并把它赋给了一个新的引用person1,那么此时在内存是什么样的呢?我们来看下图所示:
此时我们可以看到通过clone方法我们在堆上克隆了一个副本,并将这个副本对象赋给了一个新的引用,并且当我们修改person1这个引用所指向对象中所包含的简单类型age的值时,person这个引用所指向对象中所包含i的简单类型age的值并没有发生改变
,那么这种情况我们便称之为深拷贝
,即当拷贝结束后,通过一个新的引用修改所拷贝的新的对象的其中的某个类型的值时,并不影响原来引用所对应的相同对象中的相同类型的值,那么此时便为深拷贝
下面我们来介绍浅拷贝:
因为之前堆上的对象内部存储都是简单类型,所以都是深拷贝,那么如果存储引用类型后,就会发生浅拷贝了,下面来看代码:
1.class Money {
- double money = 12.6;
3.}
5.class Person implements Cloneable {
-
public int age;
-
Money m = new Money();
-
@Override
-
//此处的clone方法必须抛出CloneNotSupportedException异常
-
protected Object clone() throws CloneNotSupportedException
必看视频!获取2024年最新Java开发全套学习资料 备注Java
{
-
return super.clone();
-
}
14.}
16.public class TestDemo {
-
public static void main(String[] args) throws CloneNotSupportedException {
-
Person person = new Person();
-
Person person1 = (Person) person.clone();
-
System.out.println(person.m.money);
-
System.out.println(person1.m.money);
-
System.out.println("===========对比===========");
-
person1.m.money = 99.9;
-
System.out.println(“修改后为”+person.m.money);
-
System.out.println(“修改后为”+person1.m.money);
-
}
27.}
此时我们新建了一个Money类,并在Person类中加入了Money类的实例,那么此时Person类的实例在堆上的存储中便会多出一个引用类型,那么来看下其在内存上的存储示意图吧:
此时我们会发现我们通过clone方法仅仅将简单类型age和引用类型m克隆了过来
,但是m所指向的对象我们并没有克隆过来,那么两者的m引用都将会指向同一个对象
此时如果我们通过person1.m.money去修改money的值为99.9时
,我们会发现原引用person
所对应的money值也随之变成了99.9,那么这种现象我们称之为浅拷贝
,即其当一个新的引用去修改其克隆过来的对象中的某个类型(此段代码为引用类型)的值后,原引用调用这个类型时便会发现这个值为新修改后的值了,并不是原来的值
。
所以上述代码中的最终结果我们会发现当其中一个引用修改了值后,另一个引用去调用时也会得到相同的值,我们来看运行结果以证实我们的猜想吧
果然就算修改过后两者的值依然相等
将深拷贝改为浅拷贝的方法
此时就会有同学有疑问了,上述代码如何可以实现深拷贝呢?
答案非常简单:此时我们让Money类实现Cloneable接口就好。目的就是为了将Money类的实例也能通过clone方法拷贝过来,下面来看代码:
1.class Money implements Cloneable {
-
double money = 12.6;
-
@Override
-
protected Object clone() throws CloneNotSupportedException {
-
return super.clone();
-
}
8.}
最近我根据上述的技术体系图搜集了几十套腾讯、头条、阿里、美团等公司21年的面试题,把技术点整理成了视频(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分
clone();
- }
8.}
[外链图片转存中…(img-653cka5y-1716437698268)]
最近我根据上述的技术体系图搜集了几十套腾讯、头条、阿里、美团等公司21年的面试题,把技术点整理成了视频(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分
[外链图片转存中…(img-LgaVlp0N-1716437698269)]