谈谈 Java 的克隆

转载自  谈谈 Java 的克隆

为什么要克隆对象

做开发很少用到克隆的。我能想得到的是用于调用方法时作为参数传递,为了保证方法调用前后对象的内部结构不被破坏,可以克隆一个对象作为参数传递。

使类具有克隆能力

有人可能注意到 Object 类中有一个 native 方法clone

protected native Object clone() throws CloneNotSupportedException;

访问修饰符是 protected,缺省的情况下Object 及其子类对象无法在别的类中访问 clone(),等同于所有类缺省没有克隆能力。

要具备克隆能力,必须实现 Cloneable 接口:

public interface Cloneable {
}

奇怪的是,这个接口是空的。然而不用想那么多,这只是个标记而已,同为标记接口的还有 java.io.Serializable 等。

Cloneable 存在有两个理由:

  1. 出于安全考虑,不想让所有的类都具有克隆能力,要求若想克隆必须实现此接口;

  2. 某个引用向上转型为基类后,你就不知道它是否能克隆,此时可以使用 instanceof 关键字检查该引用是否指向一个可克隆的对象。

要具备克隆能力,必须重写父类的 clone() 方法,同时将访问修饰符改为 public,必须使用 super.clone() 进行(浅)克隆。

super.clone() 做了什么

Object 中的 clone() 识别你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。

需要注意的是这里的复制是浅层复制(浅层克隆 shadow clone),下面举一个浅层复制的例子:

public class Student implements Cloneable {private int age;private String name;private Teacher teacher;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Teacher getTeacher() {return teacher;}public void setTeacher(Teacher teacher) {this.teacher = teacher;}@Overridepublic String toString() {return "Student{" +"age=" + age +", name='" + name + '\'' +", teacher=" + ((getTeacher() == null)?"未知":getTeacher().getName()) +'}';}public Object clone(){try {return super.clone();} catch (CloneNotSupportedException e) {return null;}}}
public class Teacher {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
public class CloneTest {public static void main(String[] args){Student s1 = new Student();s1.setAge(20);s1.setName("xiaoming");Teacher teacher = new Teacher();teacher.setName("wang");s1.setTeacher(teacher);System.out.println(s1);Student s2 = (Student) s1.clone();System.out.println(s2);s1.setAge(30);s1.setName("xiaohong");teacher.setName("li");System.out.println(s1);System.out.println(s2);}}

输出为:

Student{age=20, name='xiaoming', teacher=wang}

Student{age=20, name='xiaoming', teacher=wang}

Student{age=30, name='xiaohong', teacher=li}

Student{age=20, name='xiaoming', teacher=li}

s1.setAge(30) 和 s1.setName("xiaohong") 都没有影响到克隆对象 s2。为什么? 这里说说我的理解。

基本数据类型或装箱基本数据类型在方法中作为参数传递的时候,都是传递的值的拷贝,所以单从它来讲已经做到了深层克隆。

String 类型你可以理解为是不可变的,一旦你做了改变(比如使用连接符做拼接),它也就变成另外一个对象了,不会影响到原对象,所以单从它来讲也做到了深层克隆。

teacher.setName("li") 影响到了克隆对象 s2,所以整个学生对象的克隆是浅层克隆。想要实现深层克隆,做以下修改:

public class Teacher implements Cloneable {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public Object clone(){try {return super.clone();} catch (CloneNotSupportedException e) {return null;}}
}

Student的clone()修改为:

public Object clone() {try {Student student = (Student)super.clone();student.setTeacher((Teacher)student.getTeacher().clone());return student;} catch (Exception e) {return null;}
}

输出为:

Student{age=20, name='xiaoming', teacher=wang}

Student{age=20, name='xiaoming', teacher=wang}

Student{age=30, name='xiaohong', teacher=li}

Student{age=20, name='xiaoming', teacher=wang}

通过序列化进行深层拷贝

按照上面的深层克隆方法,如果类的结构不同,clone() 代码逻辑就不同,而且还可能涉及到大量的遍历和判断等复杂的操作。

嫌麻烦? 试试用序列化做深层拷贝吧。将对象进行序列化后再进行反序列化,其效果相当于克隆对象。

下面改改代码来证明这句话:

public class Student2 implements Serializable {private static final long serialVersionUID = -4890130009355939897L;private int age;private String name;private Teacher2 teacher;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Teacher2 getTeacher() {return teacher;}public void setTeacher(Teacher2 teacher) {this.teacher = teacher;}@Overridepublic String toString() {return "Student2{" +"age=" + age +", name='" + name + '\'' +", teacher=" + ((getTeacher() == null)?"未知":getTeacher().getName()) +'}';}}
public class Teacher2 implements Serializable {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}}
public class CloneTest2 {public static void main(String[] args) throws Exception{Student2 s2 = new Student2();s2.setAge(20);s2.setName("xiaoming");Teacher2 teacher2 = new Teacher2();teacher2.setName("wang");s2.setTeacher(teacher2);System.out.println(s2);ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();ObjectOutputStream objOutputStream = new ObjectOutputStream(byteArrayOutputStream);objOutputStream.writeObject(s2);ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());ObjectInputStream objInputStream = new ObjectInputStream(byteArrayInputStream);Student2 s22 = (Student2) objInputStream.readObject();System.out.println(s22.toString());s2.setAge(30);s2.setName("xiaohong");teacher2.setName("li");System.out.println(s2.toString());System.out.println(s22.toString());}}

输出:

Student2{age=20, name='xiaoming', teacher=wang}

Student2{age=20, name='xiaoming', teacher=wang}

Student2{age=30, name='xiaohong', teacher=li}

Student2{age=20, name='xiaoming', teacher=wang}

几行序列化和反序列化代码,简单粗暴,适合绝大多数情况,再也不用为复杂的克隆逻辑而担忧了。



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/328735.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android启调支付宝

网上找了一个可以起吊支付宝的appdemo ,它集成了服务器端,我先将其分离为app和服务器端,保证app在接收参数后可以启调支付宝 (保证app这边是正确的 不然出错都不知道是服务器出错还是app出错),在 找网上资…

shiro-身份授权流程、案例

一、身份授权流程 首先调用Subject.isPermitted/hasRole接口,委托给SecurityManager.SecurityManager接着会委托给内部组件Authorizer.Authorizer再将其请求委托给我们的Realm去做,Realm才是真正干活的.realm将用户请求的参数封装成权限对象&#xff0c…

对Java的URL类支持的协议进行扩展的方法

转载自 对Java的URL类支持的协议进行扩展的方法JAVA默认提供了对file,ftp,gopher,http,https,jar,mailto,netdoc协议的支持。当我们要利用这些协议来创建应用时,主要会涉及到如下几个类:java.net.URL、java.net.URLConnection、InputStream。URL类默认…

在.Net项目中使用Redis作为缓存服务

最近由于项目需要,在系统缓存服务部分上了redis,终于有机会在实际开发中玩一下,之前都是自己随便看看写写,很零碎也没沉淀下来什么,这次算是一个系统学习和实践过程的总结。 和Redis有关的基础知识 Redis是一个开源的分…

中国的程序员培训是不是有问题?

内容来源于,看最下面的出处,侵删 中国技术开放日的出海团对日本进行了为期一周的访问。笔者随行了头两天,参加Slush Asia大会,并访问了Gungho和Deloitte两家企业。虽然已经在日本生活了四年,但这样的体验却甚少&#x…

后台回调支付宝

https://blog.csdn.net/u012552275/article/details/78320051 网上找了一个可以起吊支付宝的appdemo ,它集成了服务器端,我先将其分离为app和服务器端,保证app在接收参数后可以启调支付宝 (保证app这边是正确的 不然出错都不知道…

解决高版本SpringBoot整合swagger时启动报错:Failed to start bean ‘documentationPluginsBootstrapper‘ 问题

一、控制台的报错信息 2021-12-29 15:15:04 [main] ERROR org.springframework.boot.SpringApplication - Application run failed org.springframework.context.ApplicationContextException: Failed to start bean documentationPluginsBootstrapper; nested exception is j…

java图片格式转化(例如jpg格式转化png)

转载自 java图片格式转化(例如jpg格式转化png) import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Scanner;import javax.imageio.*; public class FormatConversion {public static final Str…

微软开源PowerShell并支持Linux

建议在Wifi 环境下观看视频 class"video_iframe" data-vidtype"1" style" z-index:1; " height"375" width"500" frameborder"0" data-src"https://v.qq.com/iframe/preview.html?vidv0322g7kd3f&width…

招银网络科技笔试

记录一下 招银网络笔试 2017年09月11日 14:32:53 阅读数:2450 Part1. 30道单选 涉及Java,C,多线程,算法,数据结构,CPU,NP问题,SQL语句,IP地址转换,行测。…

mybatisGenerator逆向工程

一、在pom文件中导入依赖和generator插件 <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><dependency&…

2016最佳温情小说:雨还在下....

作者 | 李德霞 来源 | 小小说选刊 哗&#xff0c;一道闪电&#xff1b;轰&#xff0c;一个响雷。 暴雨倾盆&#xff0c;天地间浑沌一片…… 老大扑腾腾坐起来&#xff0c;心也跟着扑腾腾地跳。老大拉亮灯&#xff0c;推推身边的媳妇。媳妇一骨碌爬起来&#xff0c;咋&#xf…

java 中 image 和 byte[] 相互转换

转载自 java 中 image 和 byte[] 相互转换只需要一个存储了图片信息的二进制串&#xff08;byte[]&#xff09; 然后&#xff0c;这样&#xff1a; InputStream buffin new ByteArrayInputStream(/*二进制串*/, /*起始位置*/,/*二进制串长度*/)); BufferedImage img ImageIO…

招银网络

记录一下 招银网络笔试 2017年09月11日 14:32:53 阅读数&#xff1a;2451 Part1. 30道单选 涉及Java&#xff0c;C&#xff0c;多线程&#xff0c;算法&#xff0c;数据结构&#xff0c;CPU&#xff0c;NP问题&#xff0c;SQL语句&#xff0c;IP地址转换&#xff0c;行测。…

Java 文件和byte数组转换

转载自 Java 文件和byte数组转换 /** * 获得指定文件的byte数组 */ private byte[] getBytes(String filePath){ byte[] buffer null; try { File file new File(filePath); FileInputStream fis new FileInputStream(file); ByteArrayOutputStream bos new ByteAr…

json大文件导入数据库

json文件导入数据库 使用Navicat的客户端工具也可以实现json文件导入数据库&#xff0c;但是数据量大了之后&#xff0c;字段的值过于冗长可能会导致数据的截取&#xff0c;是的数据导入不是完整的。 所以另辟蹊径使用其他方法 创建一个新的工程用原始的jdbc实现数据的导入 一…

Docker for Windows使用简介

在上一篇文章中&#xff0c;通过演练指导的方式&#xff0c;介绍了在Docker中运行ASP.NET Core Web API应用程序的过程。本文将介绍Docker for Windows的使用。 先决条件 前两周时间&#xff0c;Docker发布了Docker for Windows的正式版&#xff0c;于是就可以在Windows下运行D…

pagehelper 不分页的解决方法

pagehelper 不分页的解 pagehelper PageHelper.startPage(1, 10);只对该语句以后的第一个查询语句得到的数据进行分页, 就算你在PageInfo pa new PageInfo("",对象);语句里面的对象是写的最终得到的数据,该插件还是只会对第一个查询所查询出来的数据进行分页 第一…

最近流行的12个笑话,好笑又有道理

来源 | 悦读文摘&#xff08;ID&#xff1a;yueduwz&#xff09; 01 一个盲人到亲戚家做客&#xff0c;天黑后&#xff0c;他的亲戚好心为他点了个灯笼&#xff0c;说&#xff1a;“天晚了&#xff0c;路黑&#xff0c;你打个灯笼回家吧&#xff01;” 盲人火冒三丈地说&#x…

java 从jar包中读取资源文件

转载自 java 从jar包中读取资源文件 在代码中读取一些资源文件(比如图片&#xff0c;音乐&#xff0c;文本等等)&#xff0c;在集成环境(Eclipse)中运行的时候没有问题。但当打包成一个可执行的jar包&#xff08;将资源文件一并打包&#xff09;以后&#xff0c;这些资源文件找…