转载自  谈谈 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}

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

谈谈 Java 的克隆相关推荐

  1. 谈谈 Java 类加载机制

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 来源:Rainstorm , github.com/c-rainstorm/blog/blob/m ...

  2. 谈谈Java程序员进阶的那些知识和方向

    谈谈Java程序员进阶的那些知识和方向 记得前段时间看过一篇文章谈到一种程序员叫野生程序员,战斗力极强,可以搞定一切问题,但是通常看问题抓不到本质,或者说是google/baidu/stackover ...

  3. java中怎样克隆,如何在Java中克隆列表?

    要克隆Java中的列表,最简单的方法是使用ArrayList.clone()方法- 示例import java.util.ArrayList; public class Demo { public s ...

  4. Android 性能优化 之谈谈Java内存区域

    最近一年副业主要在学习投资和技能学习,把以前学习内存分析的一些笔记总结发出来,写了很多笔记总结都没有写完就又忙着了,最近再次总结复习学习一遍,还有提醒各位同学一定要学会投资.. 了解Android 内 ...

  5. java深度克隆_Java深入学习26:Java深度克隆

    Java深入学习26:Java深度克隆 深克隆和浅克隆区别 浅克隆: 只copy对象引用,不copy对象本身.即对象地址不变,仍然只存在一个对象. 深克隆: 不仅拷贝对象本身,而且拷贝对象包含的引用指 ...

  6. java多核并行计算_谈谈Java任务的并行处理

    前言 谈到并行,我们可能最先想到的是线程,多个线程一起运行,来提高我们系统的整体处理速度:为什么使用多个线程就能提高处理速度,因为现在计算机普遍都是多核处理器,我们需要充分利用cpu资源:如果站的更高 ...

  7. java stream 求和_谈谈Java任务的并行处理

    作者:ksfzhaohui 前言 谈到并行,我们可能最先想到的是线程,多个线程一起运行,来提高我们系统的整体处理速度:为什么使用多个线程就能提高处理速度,因为现在计算机普遍都是多核处理器,我们需要充分 ...

  8. 谈谈JAVA中的安全发布

    谈谈JAVA中的安全发布 昨天看到一篇文章阐述技术类资料的"等级",看完之后很有共鸣.再加上最近在工作中越发觉得线程安全性的重要性和难以捉摸,又掏出了<Java并发编程实战& ...

  9. 谈谈Java虚拟机——Class文件结构

    谈谈Java虚拟机--Class文件结构 大家都知道,Java之所以如此受人喜欢,很大的原因是要规于它的跨平台性."一次编写,到处运行",Java诞生之时曾提出的著名的宣传口号,充 ...

最新文章

  1. java 导出 jar lib_java使用IDEA引入外部jar和导出可执行jar文件的方法
  2. X86虚拟化之三种服务器虚拟化战略架构
  3. LightGBM安装与模型训练
  4. [ ZJOI 2012 ] 灾难
  5. vs2013 编译libevent32和64bit
  6. repo 获取各个库的tag代码或者分支代码
  7. devops定义_Coffee Shop DevOps:明确定义和传达团队目标
  8. 微课|玩转Python轻松过二级(2.2.1节):算术运算符
  9. 【进阶】【转】项目经理常用工具
  10. h5在微信自定义分享php,h5页面自定义微信分享
  11. centos 5.8 mysql_linux centos5.8装yum安装mysql
  12. 人工智能数学基础--概率与统计8:一个很有意思的下棋输赢概率问题
  13. PS教程淘宝美工平面设计入门自学课 photoshop软件零基础视频大全
  14. 读 稻盛和夫《干法》
  15. java报错root cause_[Filtered request failed.] with root cause java.io.OptionalDataException
  16. H5 微信小游戏群 openGID 解密
  17. 番茄炖牛腩做法,味美汤浓开胃爽口,牛腩入口即化,太下饭!
  18. 将quantopian的动量策略迁移到老虎证券量化api
  19. 用CSS实现一个聚光灯效果
  20. 移动小人Python程序

热门文章

  1. qt 调用qpainter_在Qt5.4中如何实现QOpenGLWidget和QPainter混合编程
  2. 二叉树的存储结构及四种遍历(C语言)
  3. 第七届蓝桥杯决赛真题 - 凑平方数-全排列+dfs+set去重
  4. maven知识提炼总结
  5. autowired用在static_java – @Autowired和static方法
  6. echarts字变大_在echarts中如何调整lable的字体大小?
  7. A. Slackline Adventure(思维 + 莫比乌斯)(2018-2019 ACM-ICPC Brazil Subregional Programming Contest)
  8. Codeforces Round #624 (Div. 3) D. Three Integers 数论
  9. 【杭电多校2020】Fibonacci Sum【斐波拉契通项】【推式子】
  10. JavaWeb --第四章Maven详解