Java如何实现浅克隆与深克隆_Java浅谈克隆clone
为什么需要克隆clone
若需修改一个对象,同时不想改变调用者的对象,就要制作该对象的一个本地副本。这里我们讨论的是对象,如果是基本数据类型,就很简单了,只需要重新定义一个变量名称然后赋值即可。如果是一个对象,可能有些人说了,我直接new一个新的对象就可以了,这确实是一种解决方式,可是有一些在开发中需要使用的对象经过若干逻辑其中的属性早已经不再是初始值了。如果new不行,直接重新声明一个新名称使用"="赋值当然也不可以,对象如果使用"="赋值,那么两个对象在内存中会指向同一个地址,最终结果就是新对象所做的任何改动都会影响原对象中的值。制作本地副本简单的一种方式就是使用Java自身提供的clone()方法,该方法位于Object类中,权限修饰符是protected。Clone就是克隆的意思,即制作一个一模一样的副本。克隆有最常见的有两种方式即:深克隆和浅克隆,有时也叫做深复制和浅复制。
克隆clone简介
浅克隆
被克隆对象中所有变量的值都含有和原来对象相同的值,请记住这里所说的是值都是相同的。如果原对象中的变量是基本数据类型的,该变量会复制一份跟克隆对象,但是如果是引用类型的话,则会将该引用类型的地址复制一份给克隆对象,原对象和克隆对象中该成员变量的指向相同的内存地址。也就是说如果修改克隆对象中引用类型的值,同样会影响原对象中引用类型的值,反之原对象修改引用类型的值,克隆对象中引用类型值也会跟随着改变。
在Java中通过Object的clone()方法默认实现的就是浅克隆。
深克隆
类似浅克隆,被克隆对象中所有变量的值都含有和原来对象相同的值,不仅仅值是相同的,这里如果成员变量是引用类型的话,该引用类型的对象也会同样复制一份,也就是说不仅对象本身会复制一份,对象所持有的所有成员变量本身也都会复制一份。深克隆的对象如果成员变量是引用类型的话,一旦该引用类型的值被改变不会影响原对象中对应类型的值,同样如果原对象中引用类型值改变也不会影响克隆对象中的值,也就是说深克隆的时候除了克隆对象本身值看上去跟原对象相同之外不再具有任何关系。
在Java中有两种方式可实现深克隆,一种是通过Object的clone()方法,另外一种就是通过Serializable接口。
Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。
克隆clone方法
clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:
对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象
对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
如何实现对象的克隆
在子类中覆盖Object类的clone()方法,并声明为public。
在子类的clone()方法中,调用super.clone()。
在子类中实现Cloneable接口。
克隆示例
使用clone方法
使用克隆方法既可以实现浅克隆,也可以实现深克隆。首先看一个浅克隆的示例,然后逐步深入进行深克隆,先定义一个地址类Address,然后再定义一个学生类Student,在学生类Student中包含了一个地址类Address的引用,我们让学生类Student实现Cloneable接口,然后重写clone()方法,并将clone()方法访问控制符更改为public。
public class Address{
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return address;
}
}
public class Student implements Cloneable {
private int number;
private Address address;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public String toString() {
return "Student [number=" + number + ", address=" + address + "]";
}
}
Student student=new Student();
student.setNumber(1001);
Address address=new Address();
address.setAddress("street01");
student.setAddress(address);
Student cloneStu=(Student) student.clone();
cloneStu.setNumber(1002);
cloneStu.getAddress().setAddress("street02");
System.out.println(student);//Student [number=1001, address=street02]
System.out.println(cloneStu);//Student [number=1002, address=street02]
从运行的结果可以看出,克隆后的Student对象对地址Address更改后影响了原对象,这种结果很显然不是我们想要的,这种方式就是所说的浅克隆。那么如何才能够让克隆后的对象使用引用时不影响原对象呢?事实上还是使用clone()方法,这时候需要原对象中的每一个非基本数据类型的所对应的类都要实现Cloneable接口,并且重写clone()方法,这种方式就是使用clone()方法实现的深克隆,从这里可以看出来一个弊端,如果原对象中一个属性是引用类型,但是该引用类型中还包含另外一种引用类型,以此类推,如果使用clone()方式就会相当麻烦,在重写clone()方法的时候就会导致调用一层又一层,很容易出现问题。这也解释了为什么会有另外一种使用序列化Serializable进行深度克隆的问题,因为使用序列化Serializable的时候不需要重写clone()方法了。
按照上面的思路继续使用clone()来实现深克隆,首先将Address类实现Cloneable接口,然后重写clone()方法。
public class Address implements Cloneable{
...
@Override
protected Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
然后在Student类中对每一种引用类型都对克隆后的对象进行重新赋值。
public class Student implements Cloneable {
...
@Override
public Object clone() {
Student stu = null;
try {
stu = (Student) super.clone();
if (null != address) {
stu.address = (Address) address.clone();
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
然后我们再重新运行结果如下:
Student [number=1001, address=street01]
Student [number=1002, address=street02]
使用序列化Serializable克隆
使用序列化Serializable进行克隆一定是执行的深克隆,上面也进行了简单分析,如果纯粹的使用clone()方法进行深克隆,一旦我们要克隆的对象嵌套太多层次执行起来就相当繁琐,而使用序列化Serializable进行深克隆并不关心该对象有多少层引用嵌套。简单的同时就是比较耗时,而clone()方法我们点击查看源代码可以知道它实际上是一个native方法,特点就是执行起来比较快。但是在使用Serializable类时很虽然很容易设置,但在复制它们时却要做多得多的工作,虽然可以不写入文件,但是它确确实实是一个IO操作,耗时是难免的,这也从侧面说明该方式实现clone()应用并不是非常广泛。依赖其操作的简单性,开发中偶尔还是非常有用的,Apache的common-lang包下有一个类SerializationUtils,提供了专门的方法供Serializable序列化深克隆使用。
继续使用上面的示例进行介绍,我们将两个类进行序列化,不需要再实现Cloneable接口了,也不需要重写clone()方法,然后使用如下方法进行深度克隆即可。
public static T clone(final T object) {
if (object == null) {
return null;
}
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
@SuppressWarnings("unchecked")
final T readObject = (T) in.readObject();
return readObject;
} catch (final ClassNotFoundException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
return null;
}
Student student=new Student();
student.setNumber(1001);
Address address=new Address();
address.setAddress("street01");
student.setAddress(address);
Student cloneStu=Utils.clone(student);
cloneStu.setNumber(1002);
cloneStu.getAddress().setAddress("street02");
System.out.println(student);//Student [number=1001, address=street01]
System.out.println(cloneStu);//Student [number=1002, address=street02]
参考资料
Java如何实现浅克隆与深克隆_Java浅谈克隆clone相关推荐
- Java中的浅克隆与深克隆
Java中的浅克隆与深克隆 一:前言 二:浅克隆与深克隆的区别 一:前言 克隆,即复制一个对象,该对象的属性与被复制的对象一致,如果不使用Object类中的clone方法实现克隆,可以自己new出一个 ...
- Java架构师成长之道之浅谈计算机系统架构
Java架构师成长之道之浅谈计算机系统架构 Java架构师成长之旅 1.1 信息技术发展趋势 目前信息技术主要经历了互联网.移动互联网以及以大数据.云计算.人工智能和区块链为代表的新兴技术三个阶段.而 ...
- java 线程aba,浅谈Java中ABA问题及避免,浅谈javaaba避免
浅谈Java中ABA问题及避免,浅谈javaaba避免 本文主要研究的是关于Java中ABA问题及避免的相关内容,具体如下. 在<Java并发实战>一书的第15章中有一个用原子变量实现的并 ...
- 详细分析Java中的浅克隆和深克隆
本文对浅克隆和深克隆的两种方法(不引入别的开源工具)进行了简单的代码实现(没有内部类语法),对比了浅克隆和深克隆对引用类型的影响,暂不考虑不可变类,确保初学Java者能够看懂并学会,可直接复制源代码进 ...
- java程序的装载与检查_浅谈Java类型装载、连接与初始化
类型装载.连接与初始化 Java虚拟机通过装载.连接和初始化一个Java类型,使该类型可以被正在运行的Java程序所使用.其中装载就是把二进制形式的Java class文件读入Java虚拟机中去;连接 ...
- java接口与类相同不同_浅谈java的接口和C++虚类的相同和不同之处
C++虚类相当于java中的抽象类,与接口的不同之处是: 1.一个子类只能继承一个抽象类(虚类),但能实现多个接口 2.一个抽象类可以有构造方法,接口没有构造方法 3.一个抽象类中的方法不一定是抽象方 ...
- java中修饰常量的事_浅谈java中的声明常量为什么要用static修饰
今天定义一个类常量,想着也只有这个类可以用到,就没用static关键字修饰.结果sonar代码检查提示: Rename this field "PERSON_TYPE_USER" ...
- java编译异常和运行时异常_浅谈异常结构图、编译期异常和运行期异常的区别...
异常处理一般有2种方式,要么捕获异常try-catch,要么抛出异常throws 如果一个方法后面抛出一个运行时期异常(throws RuntimeException),调用者无须处理 如果一个方法后 ...
- java多态主要体现在哪方面,浅谈java多态的实现主要体现在哪些方面
thinking in java3中的多态 People are often confused by other, non-object-oriented features of Java, like ...
- java对比swith和if的不同,浅谈选择结构if语句和switch语句的区别
1.选择结构if语句格式及其使用 A:if语句的格式: if(比较表达式1) { 语句体1; }else if(比较表达式2) { 语句体2; }else if(比较表达式3) { 语句体3; } . ...
最新文章
- 【teradata】强制解锁
- MongoDB基础命令
- Tensor数据相关的运算、函数讲解及与numpy区别
- iPhone Development Blog系列: 如何制作服务条例窗口
- 句句真研—每日长难句打卡Day2
- java 下载junit的jar包_junit jar包下载-Junit4 jar包下载 --pc6下载站
- 盘点10款逆天级效率工具,能帮创业公司节省50%时间成本
- B站C语言字符动画原理,最初B站会员答题有多么“丧心病狂”?
- 闪迪MicroSD卡无法格式化修复经验(不一定管用)
- 服务器架设无限流量,云服务器架设无限流量
- 苏州科技大学的计算机专业怎么样,苏州科技大学怎么样?有什么王牌专业?排名如何?...
- solidity Error:linearization of inherintance graph impossable
- Zemax-多重结构的公差分析
- 未签收延误一天的快递单号是怎么查找的
- 纪录大华的智能回调需要用到的事件列表
- 【数理几何】幂函数、三角函数与椭圆方程的神奇组合
- 《码农翻身》读书笔记
- 中台战略全解读(三):业务中台建设
- 国产32位单片机使用-APT32F102x
- 1823. 找出游戏的获胜者
热门文章
- Nginx解决history模式下页面刷新404
- There appears to be trouble with your network connection
- 厘米换算英寸英尺(PTA题解)
- 千人千面、个性化推荐:解读数据赋能商家背后的AI技术
- php如何做拆弹,拆弹有多难?千万别被《拆弹专家2》给骗了,不可能遇到这种炸弹...
- kali-top10-Nmap
- 2021云蓝课c++填空第4题(B)
- 北京消费者买15类节能商品可获补贴 单件最高800元
- 云上PDF怎么删除页眉页脚_PDF怎么删除页面?
- 全球研究:持续绩效管理可提高竞争优势