Java中的深克隆和浅克隆的原理及三种方式实现深克隆
lipiwang 2024-10-15 18:43 9 浏览 0 评论
本文详细介绍了Java中的浅克隆和深克隆的概念,及案例演示如何实现深克隆!
1 克隆概述
Java中实现对象的克隆分两种一种是浅克隆一种是深克隆。首先java中Clone方法对于对象克隆的机制是:对象的基本数据类型的成员变量会被全部复制,引用类型的成员变量不会复制,只会复制该变量的引用,这样被克隆对象的引用类型的成员变量还是指向了原对象的同名引用类型的成员变量的堆内存空间,对其中一个对象的引用类型成员变量的修改会影响到另外一个被克隆对象或者源对象的引用类型的成员变量。
浅克隆:最普遍的克隆,即对象实现cloneable接口和重写clone方法,然后调用一次内部不做改写的clone方法克隆出一个对象,如果源对象内部存在引用类型的成员变量,那么就说该克隆是浅克隆,即对于引用类型属性,只克隆引用,两个对象的引用指向同一块内存地址,即同一个对象。
深克隆:基本数据类型变量和引用类型变量指向的对象都会被复制,即针对引用类型的成员变量真正的复制一份,重新开辟空间保存,这样两个引用类型属性互不影响。
2 深克隆实现
实现深克隆的方法有三种:
- 重写clone方法,clone中嵌套clone 这种方法的原理其实就是在需要克隆的对象以及该对象的引用类型的变量的类中全部实现cloneable接口,否则抛出CloneNotSupportedException将引用类型的变量也克隆一份。实际的操作上就是改写源对象的clone方法,在其内部嵌套克隆方法。 首先让源对象调用克隆方法获得克隆的对象,然后获得被克隆对象的引用类型的成员变量对象,对该成员变量对象调用克隆方法,此时成员变量对象也被克隆了一份,最后将该克隆的成员变量对象,设置为克隆对象的新的成员变量,再返回该被克隆的对象,即可实现深克隆。
- 使用序列化流 其原理是:首先使要序列化的对象和该对象的引用类型成员变量对象的类都实现Serializable接口,将对象序列化到输出流中,然后再反序列化为对象就完成了完全的复制操作了,反序列化对象类似于运行新对象的构造方法。一般序列化到外部文件,此时只需要克隆对象,并不需要外部文件,因此我们的序列化和反序列化也应该在内存中进行最好,因此还使用到在内存操作数据的流ByteArrayOutputStream和ByteArrayInputStream,他们的输出和读取都默认是在内存的数组中操作。 首先创建一个ByteArrayOutputStream内存数组输出流,创建一个ObjectOutputStream序列化流,并传入内存数组输出流,使用序列化流的writeobject方法将要序列化的对象写入内部数组中,然后创建一个ByteArrayInputStream内存数组读取流,传入一个读取数据的数组,这个数组通过内存数组输出流的toByteArray方法获得,这个数组里面的数据其实就是已经被序列化成二进制数据的对象。最后创建一个ObjectInputStream反序列化流,并传入内存数组读取流,使用反序列化流的readobject方法将数组中的对象的信息,反序列化出来。反序列化出的对象就是一个新的对象,完成了深克隆。 当然还可以固定要被序列化对象的版本号,定义一个private static final long serialVersionUID,但需要注意静态的成员和transient关键字修饰的成员不能被序列化。
- 使用开源工具类 比如Json工具类(先转换为Json字符串,然后再转换为对象)、Spring的BeanUtils(Spring项目中使用比较方便)、Cglib的BeanCopier(速度最快)、Apache的BeanUtils(该工具类比较慢,谨慎使用!)
3 案例
这里为了方便,没有使用get、set方法。
3.1 测试普通clone方法--浅克隆
public class Teacher implements Cloneable {
private String name;
private int age;
private Student stu;
public static void main(String[] args) throws CloneNotSupportedException {
Student stu = new Student("李四", 24);
Teacher tea = new Teacher("张三", 30, stu);
//使用未做改变的clone方法
Teacher teaClone = (Teacher) tea.clone();
/*clone之后改变原对象的数据*/
//改变stu的数据
stu.name="李四改";
//改变tea的数据
tea.name="张三改";
//结果被克隆的数据的内部类的stu数据也受到了影响,说明未重写的clone方法,实现的只是浅克隆,tea的对象类型属性stu还是指同一个对象
System.out.println(teaClone);
System.out.println(tea);
}
public Teacher(String name, int age, Student stu) {
super();
this.name = name;
this.age = age;
this.stu = stu;
}
public static class Student implements Cloneable {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + ''' +
", age=" + age +
", stu=" + stu +
'}';
}
}
3.2 使用重写后的clone方法--深克隆
先让外部类和内部类都重写clone方法,然后改写clone方法:
public class Teacher implements Cloneable {
private String name;
private int age;
private Student stu;
public static void main(String[] args) throws CloneNotSupportedException {
Student stu = new Student("李四", 24);
Teacher tea = new Teacher("张三", 30, stu);
//使用重写的的clone方法
Teacher teaClone = (Teacher) tea.clone();
/*clone之后改变原对象的数据*/
//改变stu的数据
stu.name="李四改";
//改变tea的数据
tea.name="张三改";
//结果被克隆的数据的内部类的stu数据没受到了影响,说明重写的clone方法,实现的是深克隆,tea的对象类型属性stu是指不同对象
System.out.println(teaClone);
System.out.println(tea);
}
@Override
protected Object clone() throws CloneNotSupportedException {
//改写clone方法
Teacher tea = (Teacher) super.clone();
//获取属性对象,再clone一次,让后设置到被克隆的对象中,返回
tea.stu = ((Student) tea.stu.clone());
return tea;
}
public Teacher(String name, int age, Student stu) {
super();
this.name = name;
this.age = age;
this.stu = stu;
}
public static class Student implements Cloneable {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + ''' +
", age=" + age +
", stu=" + stu +
'}';
}
}
这种方法对于不能重写的类,比如数组不适用!
3.3 使用序列化流--深克隆
使得两个类实现Serialzable接口,clone方法可以不要了,使用序列化流。
public class Teacher implements Serializable {
private String name;
private int age;
private Student stu;
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student stu = new Student("李四", 24);
Teacher tea = new Teacher("张三", 30, stu);
//内存数组输出流
ByteArrayOutputStream bao = new ByteArrayOutputStream();
//序列化流
ObjectOutputStream oos = new ObjectOutputStream(bao);
//将数据tea写入序列化流中,随后会被传递到内存数组输出流中,将对象序列化为byte[]类型的数据
oos.writeObject(tea);
//从内存数组输出流中获取到tea的byte[]类型的数据,传入内存数组输入流
ByteArrayInputStream bai = new ByteArrayInputStream(bao.toByteArray());
//将内存数组输入流传给反序列化流,这样也实现了byte[]类型的数据的传递
ObjectInputStream ois = new ObjectInputStream(bai);
//使用readObject,从反序列化流中读取数据,将byte[]类型的数据反序列化成Teacher对象
Teacher teaClone = (Teacher) ois.readObject();
//改变stu的数据
stu.name = "李四改";
//改变tea的数据
tea.name = "张三该";
//结果被克隆的数据的内部类的stu数据没有受到了影响,说明重写后的clone方法,实现了深克隆
System.out.println(teaClone);
System.out.println(tea);
}
public Teacher(String name, int age, Student stu) {
super();
this.name = name;
this.age = age;
this.stu = stu;
}
public static class Student implements Serializable {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + ''' +
", age=" + age +
", stu=" + stu +
'}';
}
}
3.4 使用开源工具
这里只介绍Json工具:Gson的使用。
//maven依赖
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
也可以使用jar包形式,引入Gson工具:Gson jar包下载
public class Teacher {
private String name;
private int age;
private Student stu;
public static void main(String[] args) {
Student stu = new Student("李四", 24);
Teacher tea = new Teacher("张三", 30, stu);
/*使用Gson工具*/
Gson gson = new Gson();
//将对象序列化为json字符串
String teaStr = gson.toJson(tea);
//然后将字符串反序列化为对象
Teacher GsonTea = gson.fromJson(teaStr, Teacher.class);
/*clone之后改变原对象的数据*/
//改变stu的数据
stu.name = "李四改";
//改变tea的数据
tea.name = "张三改";
/*结果被克隆的数据的内部类的stu数据没受到了影响,说明使用JSON工具,实现的是深克隆,tea的对象类型属性stu不是指向同一个对象*/
System.out.println(GsonTea);
System.out.println(tea);
}
public Teacher(String name, int age, Student stu) {
super();
this.name = name;
this.age = age;
this.stu = stu;
}
public static class Student {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + ''' +
", age=" + age +
", stu=" + stu +
'}';
}
}
从结果来看,我们并没有实现Cloneable和Serializable接口,但是对结果并没有影响。
作者:刘Java
链接:https://juejin.cn/post/6986071906273198094
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关推荐
- 软件测试|MySQL CROSS JOIN:交叉连接的详细解析
-
简介在MySQL数据库中,CROSSJOIN是一种用于生成两个或多个表的笛卡尔积的连接方法。CROSSJOIN不需要任何连接条件,它将左表的每一行与右表的每一行进行组合,从而生成一个包含所...
- 「MySQL笔记」left join-on-and 与 left join-on-where 的区别
-
1.摘要关于这两种写法的重要知识点摘要如下:left-join时,即使有相同的查询条件,二者的查询结果集也不同,原因是优先级导致的,on的优先级比where高on-and是进行韦恩运算连接...
- MySQL中的JOIN——联合查询的基本语法
-
MySQL中的JOIN指令用来将两个或多个表中的数据进行联合查询,根据连接条件来匹配记录,从而得到需要的结果集。在MySQL中,常见的JOIN类型包括INNERJOIN、LEFTJOIN和RIGH...
- MySQL 中的 CROSS JOIN:强大的连接工具
-
CROSSJOIN在MySQL里是一种挺特别的连接操作,它能弄出连接表的笛卡尔积。这就是说,要是表A有m行,表B有n行,那ACROSSJOINB的结果就会有m*n...
- 大厂必问:MySQL 三表 JOIN 操作的解析与性能优化,效率又如何?
-
大厂必问:MySQL三表JOIN操作的解析与性能优化策略,效率又如何?点击关注,开启技术之旅!大家好,这里是互联网技术学堂,无论你是一名程序员、设计师、还是对技术充满好奇心的普通人,都欢迎你加入...
- 面试题:MySQL 的 JOIN 查询优化(mysql查询优化方法)
-
MySQL的JOIN查询优化是提升数据库性能的关键环节。以下是综合多个技术文档的核心优化策略,按优先级和实现难度分类:一、索引优化:性能提升的基础为连接字段建立索引确保参与JOIN的列(通常...
- Flink中处理维表关联技术实现路径
-
在Flink中处理维表关联大体氛围TableSQLLookupJoin和DataStream算子函数,主要技术实现路径:I.FlinkSQL/TableAPI中的Lookup...
- 深入剖析Zookeeper原理(一)整体设计
-
1.ZK集群架构设计与特性1.ZK集群架构设计:ZK主要分为三种角色:Leader(领导者):一个Zookeeper集群同一时间只会有一个实际工作的Leader,它会发起并维护与各Follwer及...
- 多种负载均衡算法及其Java代码实现
-
首先给大家介绍下什么是负载均衡负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。负载均衡,英...
- 一分钟了解SpringCloud中的ribbon到底是什么,原理是啥?
-
1.概念ribbon是一款客户端负载均衡器,用于微服务之间的负载均衡。首先,什么是客户端负载均衡?如图,ribbon可以通过注册中心获取服务列表,然后自己执行自己的负载均衡策略来决定要访问哪个微服务,...
- Step by Step之腾讯云短信-验证码实践
-
在商城小程序和前端上线用了一阵子之后,用户提出了体验提升的需求,如忘记密码、绑定用户、快捷注册等,作为业界最佳实践的短信验证码登录、重置密码和注册等功能开发也就提上日程了,本文就以重置密码为例,将验证...
- 10分钟入门响应式:Springboot整合kafka实现reactive
-
Springboot引入Reactor已经有一段时间了,笔者潜伏在各种技术群里暗中观察发现,好像scala圈子的同仁们,似乎对响应式更热衷一点。也许是因为他们对fp理解的更深吧,所以领悟起来障碍性更少...
- 使用java随机生成有个性的用户名,LOL地名+水浒传,合计2808个
-
*随机生成用户名*取水浒传108好汉名字*取LOL地名26个,组合而成*一共可以生成2808个不同特色的用户名如果你在上网的时候,用户名难取的话,这里有很多可选择的用户名,现提供100个...
- 深入理解Math.random()的概率分布特性
-
直接上源码/***Returnsa{@codedouble}valuewithapositivesign,*返回一个带符号的double类型的数字,说人话就是返回一个非负...
- 编程英文 - 创建/生成/构建 (create/generate/build)
-
在软件开发中,create、generate和build这三个词经常被用到,它们都与"创造"或"产生"某些东西有关,但在具体使用场景和含义上有所不同。基本含义creat...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 软件测试|MySQL CROSS JOIN:交叉连接的详细解析
- 「MySQL笔记」left join-on-and 与 left join-on-where 的区别
- MySQL中的JOIN——联合查询的基本语法
- MySQL 中的 CROSS JOIN:强大的连接工具
- 大厂必问:MySQL 三表 JOIN 操作的解析与性能优化,效率又如何?
- 面试题:MySQL 的 JOIN 查询优化(mysql查询优化方法)
- Flink中处理维表关联技术实现路径
- 深入剖析Zookeeper原理(一)整体设计
- 多种负载均衡算法及其Java代码实现
- 一分钟了解SpringCloud中的ribbon到底是什么,原理是啥?
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)