本文详细介绍了Java中的浅克隆和深克隆的概念,及案例演示如何实现深克隆!

文章目录

  • 1 克隆概述
  • 2 深克隆实现
  • 3 案例
    • 3.1 测试普通clone方法--浅克隆
    • 3.2 使用重写后的clone方法--深克隆
    • 3.3 使用序列化流--深克隆
    • 3.4 使用开源工具

1 克隆概述

Java中实现对象的克隆分两种一种是浅克隆一种是深克隆。首先java中Clone方法对于对象克隆的机制是:对象的基本数据类型的成员变量会被全部复制,引用类型的成员变量不会复制,只会复制该变量的引用,这样被克隆对象的引用类型的成员变量还是指向了原对象的同名引用类型的成员变量的堆内存空间,对其中一个对象的引用类型成员变量的修改会影响到另外一个被克隆对象或者源对象的引用类型的成员变量。

浅克隆:最普遍的克隆,即对象实现cloneable接口和重写clone方法,然后调用一次内部不做改写的clone方法克隆出一个对象,如果源对象内部存在引用类型的成员变量,那么就说该克隆是浅克隆,即对于引用类型属性,只克隆引用,两个对象的引用指向同一块内存地址,即同一个对象。

深克隆:基本数据类型变量和引用类型变量指向的对象都会被复制,即针对引用类型的成员变量真正的复制一份,重新开辟空间保存,这样两个引用类型属性互不影响。

2 深克隆实现

实现深克隆的方法有三种:

  1. 重写clone方法,clone中嵌套clone

    1. 这种方法的原理其实就是在需要克隆的对象以及该对象的引用类型的变量的类中全部实现cloneable接口,否则抛出CloneNotSupportedException将引用类型的变量也克隆一份。实际的操作上就是改写源对象的clone方法,在其内部嵌套克隆方法。
      首先让源对象调用克隆方法获得克隆的对象,然后获得被克隆对象的引用类型的成员变量对象,对该成员变量对象调用克隆方法,此时成员变量对象也被克隆了一份,最后将该克隆的成员变量对象,设置为克隆对象的新的成员变量,再返回该被克隆的对象,即可实现深克隆。
  2. 使用序列化流

    1. 其原理是:首先使要序列化的对象和该对象的引用类型成员变量对象的类都实现Serializable接口,将对象序列化到输出流中,然后再反序列化为对象就完成了完全的复制操作了,反序列化对象类似于运行新对象的构造方法。一般序列化到外部文件,此时只需要克隆对象,并不需要外部文件,因此我们的序列化和反序列化也应该在内存中进行最好,因此还使用到在内存操作数据的流ByteArrayOutputStream和ByteArrayInputStream,他们的输出和读取都默认是在内存的数组中操作。
    2. 首先创建一个ByteArrayOutputStream内存数组输出流,创建一个ObjectOutputStream序列化流,并传入内存数组输出流,使用序列化流的writeobject方法将要序列化的对象写入内部数组中,然后创建一个ByteArrayInputStream内存数组读取流,传入一个读取数据的数组,这个数组通过内存数组输出流的toByteArray方法获得,这个数组里面的数据其实就是已经被序列化成二进制数据的对象。最后创建一个ObjectInputStream反序列化流,并传入内存数组读取流,使用反序列化流的readobject方法将数组中的对象的信息,反序列化出来。反序列化出的对象就是一个新的对象,完成了深克隆。当然还可以固定要被序列化对象的版本号,定义一个private static final long serialVersionUID,但需要注意静态的成员和transient关键字修饰的成员不能被序列化
  3. 使用开源工具类

    1. 比如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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}}@Overridepublic 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);}@Overrideprotected 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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}}@Overridepublic 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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}}@Overridepublic 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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}}@Overridepublic String toString() {return "Teacher{" +"name='" + name + '\'' +", age=" + age +", stu=" + stu +'}';}
}

从结果来看,我们并没有实现Cloneable和Serializable接口,但是对结果并没有影响。

Java中的深克隆和浅克隆的原理及三种方式实现深克隆相关推荐

  1. java解析遍历List集合(其实现子类)的三种方式

    java解析遍历List集合(其实现子类)的三种方式 1 使用迭代器对象 1.1 底层 1.1.1 List接口继承了Collection接口 1.1.2 而Collection接口又继承了Itera ...

  2. jupyter notebook python3路径_详解修改Anaconda中的Jupyter Notebook默认工作路径的三种方式...

    方式1. 打开Windows的cmd,在cmd中输入jupyter notebook --generate-config如下图: 可以看到路径为D:\Users--找到此路径修改jupyter_not ...

  3. JavaScript中遍历数组的for for-in和forEach三种方式

    JavaScript中遍历数组的for for-in和forEach三种方式 for循环 let arr = [1,2,3,4,5,6];for(let i = 0; i < arr.lengt ...

  4. JavaScript--------------------jQuery中.bind() .live() .delegate() .on()的区别 和 三种方式写光棒事件 动画...

    bind(type,[data],fn) 为每个匹配元素的特定事件绑定事件处理函数. $("a").bind("click",function(){alert( ...

  5. win下配置的ES中的数据在哪里可以看到?三种方式你看那种更加高大上!!!(win_Elasticsearch)

    在上一篇博客<使用logstash将Mysql中的数据导入到ElasticSearch中(详细步骤,win_Elasticsearch)>中我们提到将数据插入到es中,那我怎么知道数据是否 ...

  6. java时间戳是什么类型_java 获取时间戳的三种方式

    java 获取时间戳的三种方式 CreationTime--2018年7月13日16点29分 Author:Marydon 1.实现方式 方式一:推荐使用 System.currentTimeMill ...

  7. java 数组 源码_Java数组转List的三种方式及对比

    来源:https://s.yam.com/6wu6n 前言: 本文介绍Java中数组转为List三种情况的优劣对比,以及应用场景的对比,以及程序员常犯的类型转换错误原因解析. 一.最常见方式(未必最佳 ...

  8. java中集合对象与string互转的几种方式

    准备数据: UserInfo.java @Data @AllArgsConstructor public class UserInfo {private Long id;private String ...

  9. Struts2中jsp前台传值到action后台的三种方式以及valueStack的使用

    struts2中的Action接收表单传递过来的参数有3种方法: 如,登陆表单login.jsp: 1 <form action="login" method="p ...

最新文章

  1. log4j的配置参数
  2. 玩转html5(五)---月球绕着地球转,地球绕着太阳转(canvas实现,同样可以动哦)...
  3. Jacoco--测试覆盖率工具
  4. 使用nfs映射远程服务器磁盘目录
  5. PHP利用FPDI 制作PDF 档案 (php合并pdf, php签名pdf)
  6. SQL Server分组查询某最大值的整条数据(包含linq写法)
  7. 如何应对容器和云原生时代的安全挑战?
  8. 样本不平衡 pytorch_CVPR2019 | 面对高度不均衡数据如何提高精度?这篇文章有妙招...
  9. 浅谈激光导航对机器人自主行走的重要性
  10. C语言逻辑运算符: 和 ||
  11. 三维重建| iPad Pro2020 专业3D扫描应用程序 3D Scanner App(App Store可免费下载)
  12. 比树莓派好的linux板子,华硕Tinker Board主打“比树莓派再好一点”的开发板
  13. 详解 Python 中的 filter() 函数
  14. ASCII-from baidubaike
  15. 【2023亲测有效】Pandownload 归来!加速效果极佳!
  16. cocos做飞机大战笔记【玩家飞机移动与子弹发射】
  17. 科研经验002:如何礼貌地要代码的邮件模板
  18. 如何自建一个慕课(微课)演播室
  19. 推荐大家使用Dropbox
  20. android美图秀秀--基础

热门文章

  1. 计算机数据编程教学,计算机编程
  2. 【教你用傻瓜式免费软件做好本本电池监测】
  3. 功能极其强大,这8款 Python 机器学习库真香
  4. 学软件测试需要培训吗?
  5. c语言文件操作管理(下)
  6. ChatGPT流式传输(stream=True)的实现-OpenAI API 流式传输
  7. 弥合鸿沟:一种生成内部威胁数据的实用方法(Bridging the Gap: A Pragmatic Approach to Generating Insider Threat Data )
  8. 文化产业杂志文化产业杂志社文化产业编辑部2023年第5期目录
  9. 二十二:访问者模式(伪动态双分配)
  10. [BZOJ3275]Number(最小割)