Cloneable接口的作用与深入理解深度克隆与浅度克隆

2023-11-12

cloneable接口的作用

cloneable其实就是一个标记接口,只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。Object中clone方法:

这里有一个疑问,Object中的clone方法是一个空的方法,那么他是如何判断类是否实现了cloneable接口呢?

原因在于这个方法中有一个native关键字修饰。

   native修饰的方法都是空的方法,但是这些方法都是有实现体的这里也就间接说明了native关键字不能与abstract同时使用。因为abstract修饰的方法与java的接口中的方法类似,他显式的说明了修饰的方法,在当前是没有实现体的,abstract的方法的实现体都由子类重写,只不过native方法调用的实现体,都是非java代码编写的(例如:调用的是在jvm中编写的C的接口),每一个native方法在jvm中都有一个同名的实现体,native方法在逻辑上的判断都是由实现体实现的,另外这种native修饰的方法对返回类型,异常控制等都没有约束。 

   由此可见,这里判断是否实现cloneable接口,是在调用jvm中的实现体时进行判断的。

深入理解深度克隆与浅度克隆

首先,在java中创建对象的方式有四种:

        一种是new,通过new关键字在堆中为对象开辟空间,在执行new时,首先会看所要创建的对象的类型,知道了类型,才能知道需 要给这个对象分配多大的内存区域,分配内存后,调用对象的构造函数,填充对象中各个变量的值,将对象初始化,然后通过构造方法返回对象的地址;

      另一种是clone,clone也是首先分配内存,这里分配的内存与调用clone方法对象的内存相同,然后将源对象中各个变量的值,填充到新的对象中,填充完成后,clone方法返回一个新的地址,这个新地址的对象与源对象相同,只是地址不同。

另外还有输入输出流,反射构造对象等

下面通过几个例子来解析下浅度克隆与深度克隆的区别:

浅度克隆测试:

首先定义一个学生类

 

public class Student{
	private String name;   //姓名
	private int age;       //年龄
	private StringBuffer sex;  //性别
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public StringBuffer getSex() {
		return sex;
	}
	public void setSex(StringBuffer sex) {
		this.sex = sex;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
	}
		
}

其次定义一个学校类,类中重写clone方法

public  class School implements Cloneable{
	private String schoolName;   //学校名称
	private int stuNums;         //学校人数
	private Student stu;         //一个学生
	public String getSchoolName() {
		return schoolName;
	}
	public void setSchoolName(String schoolName) {
		this.schoolName = schoolName;
	}
	public int getStuNums() {
		return stuNums;
	}
	public void setStuNums(int stuNums) {
		this.stuNums = stuNums;
	}
	public Student getStu() {
		return stu;
	}
	public void setStu(Student stu) {
		this.stu = stu;
	}
	@Override
	protected School clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return (School)super.clone();;
	}
	@Override
	public String toString() {
		return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]";
	}
	
}

最后定义一个main类来测试一下:

public static void main(String[] args) throws CloneNotSupportedException {
	School s1 = new School();       
	s1.setSchoolName("实验小学");
	s1.setStuNums(100);
	Student stu1 = new Student();
	stu1.setAge(20);
	stu1.setName("zhangsan");
	stu1.setSex(new StringBuffer("男"));
	s1.setStu(stu1);
	System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+"  s1中stu1的hashcode:"+s1.getStu().hashCode());
	School s2 = s1.clone();  //调用重写的clone方法,clone出一个新的school---s2
	System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());
}

测试结果:

     可以看出s1与s2的hashcode不同,也就是说clone方法并不是把s1的引用赋予s2,而是在堆中重新开辟了一块空间,将s1复制过去,将新的地址返回给s2。   

      但是s1中stu的hashcode与s2中stu的hashcode相同,也就是这两个指向了同一个对象,修改s2中的stu会造成s1中stu数据的改变。但是修改s2中的基本数据类型与Stirng类型时,不会造成s1中数据的改变,基本数据类型例如int,在clone的时候会重新开辟一个四个字节的大小的空间,将其赋值。而String则由于String变量的唯一性,如果在s2中改变了String类型的值,则会生成一个新的String对象,对之前的没有影响。  这就是浅度克隆。

如何实现深度clone?(下面时第一种方法,另外使用序列化将student变成流,输入再输出也可以)

首先需要让student重写clone方法,实现cloneable接口

public class Student implements Cloneable{
	
	private String name;
	private int age;
	private StringBuffer sex;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public StringBuffer getSex() {
		return sex;
	}
	public void setSex(StringBuffer sex) {
		this.sex = sex;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
	}
	@Override
	protected Student clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return (Student)super.clone();
	}
}

然后,在school的clone方法中将school中的stu对象手动clone一下。

	@Override
	protected School clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		School s = null;
		s = (School)super.clone();
		s.stu = stu.clone();
		return s;
	}

再次执行main方法查看结果:

public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
	School s1 = new School();       
	s1.setSchoolName("实验小学");
	s1.setStuNums(100);
	Student stu1 = new Student();
	stu1.setAge(20);
	stu1.setName("zhangsan");
	stu1.setSex(new StringBuffer("男"));
	s1.setStu(stu1);
	System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+"  s1中stu1的hashcode:"+s1.getStu().hashCode());
	School s2 = s1.clone();  //调用重写的clone方法,clone出一个新的school---s2
	System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());
	
	//修改s2中的值,看看是否会对s1中的值造成影响
		 s2.setSchoolName("希望小学");
		 s2.setStuNums(200);
		 Student stu2 = s2.getStu();
		 stu2.setAge(30);
		 stu2.setName("lisi");
		 stu2.setSex(stu2.getSex().append("6666666"));
		 s2.setStu(stu2);
		 
		 //再次打印两个school,查看结果
		 System.out.println("-------------------------------------------------------------------------");
		 System.out.println("s1: "+s1+" hashcode:"+s1.hashCode()+"  s1中stu1的hashcode:"+s1.getStu().hashCode());
		 System.out.println("s2: "+s2+" hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());
}
}

打印结果:

这里可以看到两个stu的hashcode已经不同了,说明这已经是两个对象了,但是在s2中修改sex的值,为什么还会影响到s1呢?

  原因在于sex的类型是Stringbuffer,在clone的时候将StringBuffer对象的地址传递了过去,而StringBuffer类型没有实现cloneable接口,也没有重写clone方法。

这种情况应该怎么解决呢?

1.只实现浅度clone

2.stu2.setSex(new StringBuffer("newString"));  在设置stu2的sex时创建一个新的StringBuffer对象。
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Cloneable接口的作用与深入理解深度克隆与浅度克隆 的相关文章

  • 在 Java 中将系统属性设置为 Null

    在我的单元测试中 我需要将 workingDir 系统属性设置为 Null 但我不能这样做 因为它给了我 NullPointerException System setProperty workingDir null 我该怎么做 您不能将属
  • java.lang.unsatisfiedlinkerror 无法加载 amd 64 位 .dll ia 32 位

    当我尝试在 Eclipse 上运行我的项目时 出现以下错误 它在我开发它的计算机上运行良好 但当我将其导入我的笔记本电脑时 它不起作用 这个问题已经在本网站的其他地方提出过 这个问题的主要原因似乎是环境变量设置不正确 但我检查过 它们似乎是
  • 使用 Java NIO 直接访问 Windows 磁盘

    我正在使用一个使用 Java NIO 的库来直接将文件映射到内存 但我在直接读取磁盘时遇到问题 I can直接使用读取磁盘FileInputStream与 UNC 合作 例如 File disk new File PhysicalDrive
  • 在 Java 和 C 中在运行时调用名为“string”的方法

    我们如何调用名称为的方法string在运行时 谁能告诉我如何在 Java 和 C 中做到这一点 在java中可以通过反射api来完成 看一下Class getMethod String methodName Class parameterT
  • Eclipse 说“更新 Android Developer Toolkit”

    我不知何故弄乱了我的 Eclipse 和 Android 设置 我不知道如何修复它 问题症状如下 在 首选项 gt Android 中 我尝试选择 android sdk linux 的位置 选择时出现错误 此 Android SDK 需要
  • Android 背景 + 文本 + 按钮图标

    我想要一个图像设置为文本的背景 并在文本的左侧设置一个图标 在iPhone中非常简单 但不知道如何在Android上做到这一点 调整按钮的大小并保持图标 文本的位置和距离正确 iPhone 安卓我有这个 xml代码是
  • SwingWorker 在 Unsafe.park() 处挂起

    我有一个SwingWorker与后台服务器通信 然后更新JFrame 我正在调试我的应用程序并注意到即使在SwingWorker完成了它的工作 它的线程仍然存在 它挂在Unsafe park java lang Object 这是一个本机方
  • Selenium Webdriver 中显式等待 findElements

    登录后 页面重定向到一个页面 我想等待页面加载 我在其中按 tagName 查找元素 By inputArea By tagName input List
  • Hibernate HQL 查询:如何将集合设置为查询的命名参数?

    给定以下 HQL 查询 FROM Foo WHERE Id id AND Bar IN barList I set id使用查询对象的setInteger 方法 我想设置 barList用一个List对象 但查看 Hibernate 文档和
  • 找不到模块:javafx.controls

    我已经下载了JavaFX SDK 解压它并设置PATH TO FX系统变量 如下本说明 https openjfx io openjfx docs install javafx 我使用了以下代码示例 import javafx applic
  • 用dagger 2查看依赖注入

    我有一个自定义视图扩展TextView 我应该在哪里调用我的组件来注入视图 component inject customTextView 因此 我发现我需要在自定义视图的构造函数中添加注入 在所有视图中 或者使一个调用另一个 Exampl
  • Spark toLocalIterator 和迭代器方法之间的区别

    在编写 Spark 程序时我遇到了这个toLocalIterator 方法 之前我只使用iterator method 如果有人曾经使用过这种方法 请点亮 我在使用时遇到foreach and foreachPartitionSpark程序
  • 为什么 Libgdx 的 Table 不接受缩放操作?

    我在 libgdx 库中使用 scene2d 在游戏中创建一些 UI 我使用了一个表格 我想在用户触摸时采取一些缩放操作以使按钮触摸有意义 当我使用任何其他 Actor 类型 例如 Group 并为其提供缩放操作时 它可以工作 但不能工作表
  • 将 Class 对象转换为字节

    如果我有一个Class http java sun com j2se 1 5 0 docs api java lang Class html在运行时实例 我可以获得它的 byte 表示形式吗 我感兴趣的字节将在类文件格式 http java
  • 使用JPanel绘制直线并获取点坐标

    我现在完全不知所措 我没有太多用 Java 构建 GUI 我一直在阅读有关 swing 和 JPanel 的所有内容 我认为我想做的事情是可能的 我只是还没有弄清楚how 我正在尝试构建一个 GUI 您可以在其中在某个绘图区域内绘制直线 我
  • Java字符串中的字符数[重复]

    这个问题在这里已经有答案了 可能的重复 Java 使用unicode上划线显示平方根时字符串的长度 https stackoverflow com questions 7704426 java length of string when u
  • Spring @Configuration如何缓存对bean的引用

    使用基于 Java 的配置时 Spring 如何防止再次调用 bar 我想知道编译时注释处理或通过代理方法 Configuration public class AppConfig Bean public Foo foo return ne
  • 我可以从同一个 jar 文件执行两个不同的类吗?

    我有一个项目 在一个包中我制作了服务器 在第二个包中我制作了客户端 它运行良好 我想创建一个 Jar 文件 是否可以使用同一个 jar 文件分别运行客户端和服务器 我使用了只有一个 main 的 jar 文件 当我运行 jar 文件时 它会
  • @JsonCreator '无法找到具有名称的创建者属性',即使使用ignoreUnknown = true

    我有以下课程 JsonIgnoreProperties ignoreUnknown true public class Topic private List
  • 使用反射 API 填充 Proto 中的地图字段

    我正在尝试编写一个模块 该模块将获取 Message Builder 和从字段名称到值的映射 并将用值填充构建器 一切正常 直到我遇到地图字段 使用 Proto3 我收到一条特定消息 我知道我可以执行该消息的字段 builder b put

随机推荐

  • 【经验】工业以太网PROFINET从机(TPS-1)与主机(MCU)通讯初始化时序控制注意事项

    目录 一 TPS 1的复位引脚和主机MCU的复位引脚应连接到系统复位 二 应首先启动从设备 然后启动主机MCU 瑞萨电子TPS 1是支持PROFINET协议的单芯片接口元件 它集成了PROFINET CPU PROFINET协议栈 2通道以
  • C/C++基于线程的并发编程(二):线程安全和线程锁

    线程安全 所谓线程安全不是指线程的安全 而是指内存的安全 线程是由进程所承载 所有线程均可访问进程的上下文 意味着所有线程均可访问在进程中的内存空间 这也是线程之间造成问题的潜在原因 当多个线程读取同一片内存空间 变量 对象等 时 不会引起
  • Java基础——数组应用之字符串String类

    字符串String的使用 Java字符串就是Unicode字符序列 例如串 Java 就是4个Unicode字符J a v a组成的 Java中没有内置的字符串类型 而是在标准Java类库中提供了一个预定义的类String 每个用双引号括起
  • 【PYTHON并发学习】多线程Threading+多进程Multiprocessing+多协程Asyncio

    文章目录 PYTHON并发知识学习 第一节 python并发编程简介 第二节 怎样选择多线程多进程多协程 第三节 python速度慢的罪魁祸首 全局解释器GIL 第四节 使用多线程 python爬虫被加速10倍 第五节 python实现生产
  • VS调试时如何跳出for循环?

    例如这个程序 如果我单步调试时进入了第116行的for循环那么不论我们是 逐语句 F10还是 逐过程 F11还是 跳出 shift F11 都无法做到让for循环一次性执行完 解决办法 在for循环外设置断点 如 行125 gt F8跳到下
  • c/c++依赖静态库、动态库符号问题

    假设 某可执行程序exe依赖liba中的funcA和libb中的funcB liba和libb中定义了同名的函数subfunc 供funcA和funcB调用 两个库中的subfunc实现不同 一个是相加 一个是相减 liba so liba
  • Flask-响应

    响应字符串 Flask调用视图函数后 会将其返回值作为响应的内容 多数情况下 响应就是一个简单的字符串 作为html页面回送客户端 如 app route admin def hello admin return Hello Admin f
  • [系统安全] 三十.CS逆向分析 (1)你的游戏子弹用完了吗?Cheat Engine工具入门普及

    您可能之前看到过我写的类似文章 为什么还要重复撰写呢 只是想更好地帮助初学者了解病毒逆向分析和系统安全 更加成体系且不破坏之前的系列 因此 我重新开设了这个专栏 准备系统整理和深入学习系统安全 逆向分析和恶意代码检测 系统安全 系列文章会更
  • jboss jndi配置部分参数详解

    使用的是jboss7 1 1 jndi的配置在 JBOSS HOME standalone configuration standalone xml中进行配置 配置jndi时有很多参数 解释下用到的一些参数 jndi name The JN
  • k8s笔记14--初次体验 开源云原生软件交付平台zadig

    k8s笔记14 初次体验 开源云原生软件交付平台zadig 1 介绍 2 部署 测试 2 1 部署 2 2 测试 3 注意事项 4 说明 1 介绍 Zadig 是 KodeRover 公司基于 Kubernetes 自主设计 研发的开源分布
  • 杰理之PC端拷贝语音文件到外挂flash速度慢如何解决【篇】

    开启了flash拷贝缓冲buffer
  • 面向对象程序设计 第一次作业 记录

    Date类的定义及使用 题目描述 定义一个Date类 该类有year month和day三个私有成员 用于存储日期的年 月和日信息 为该类定义默认构造函数 带参的构造函数 复制构造函数和读写数据成员的函数setDate和showDate 其
  • macOS 13 Ventura系统自动开机在哪设置?

    macOS 13如何设置自动开关机 Mac电脑可以设置一个自动开机时间来让电脑自动启动 Mac设置自动开关机的方法教程 macOS13系统正式版已经发布了一段时间 不知道大家有没有升级到macOS 13 Ventura最新系统呢 在 mac
  • GitBook, Git + Markdown 快速发布你的书籍

    本文转载至 http leeluolee github io 2014 07 22 2014 07 22 gitbook guide gitbook是一个用于发布个人书籍的平台 类似于国外著名的LeanPub 其中一个很大的特点是它利用gi
  • Mac OS X 打开自动播放

    本文转载至 http www defaults write com quicktime player x enable auto play feature Quicktime Player X 默认情况下是不会自动播放了 可以通过下面的命令
  • 刘二PyTorch深度学习(五)——Logistic Regression

    1 分类问题的输出其实是一个概率 2 二分类 只有两个分类的问题 0和1 3 饱和函数 导函数图像类似于正态分布 x越接近0 图像斜率越大 离0越远 斜率越小 当超过某一阈值时 导数值越来越小 最后趋近于0 4 Logistic函数 西格玛
  • nodejs中间件

    内置中间件 express中内置的中间件 中间件本质是一个函数 json json是处理json请求的 获取参数 req body app use express json 测试json是否成功的步骤 1 在postman发起请求 2 po
  • 智能运维发展史及核心技术研究

    作者介绍陈林博 工学博士 毕业于同济大学计算机系统结构专业 目前从事基础技术框架研发 云计算研究与应用 智能运维研究与应用等工作 何支军 工程硕士 毕业于复旦大学微电子专业 现任中国结算上海分公司技术开发部总监 长期从事登记结算技术系统的建
  • 【漏洞复现】CVE-2023-25157 GeoServer OGC Filter SQL注入漏洞

    启动环境 查看端口号 访问http your ip 8080 geoserver即可查看到GeoServer的首页 在利用漏洞前 需要目标服务器中存在类型是PostGIS的数据空间 datastore 和工作空间 workspace 在Vu
  • Cloneable接口的作用与深入理解深度克隆与浅度克隆

    cloneable接口的作用 cloneable其实就是一个标记接口 只有实现这个接口后 然后在类中重写Object中的clone方法 然后通过类调用clone方法才能克隆成功 如果不实现这个接口 则会抛出CloneNotSupported