java clone原理_详解Java中的clone方法 -- 原型模式

Java中对象的创建

clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。那么在java语言中,有几种方式可以创建对象呢?

1 使用new操作符创建一个对象

2 使用clone方法复制一个对象

那么这两种方式有什么相同和不同呢?

new操作符的本意是分配内存。程序执行到new操作符时,

首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的

初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。而clone在第一步是和new

相似的,

都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,

填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

复制对象 or 复制引用

在Java中,以下类似的代码非常常见:

Person p = new Person(23, "zhang");

Person p1 = p;

System.out.println(p);

System.out.println(p1);

当Person p1 = p;执行之后, 是创建了一个新的对象吗? 首先看打印结果:

com.pansoft.zhangjg.testclone.Person@2f9ee1ac

com.pansoft.zhangjg.testclone.Person@2f9ee1ac

可已看出,打印的地址值是相同的,既然地址都是相同的,那么肯定是同一个对象。p和p1只是引用而已,他们都指向了一个相同的对象Person(23, "zhang") 。 可以把这种现象叫做引用的复制。 (关于引用和对象的区分,可以参考我之前的文章Java中的String为什么是不可变的?

-- String源码分析 , 其中有一节讲到了引用和对象的区分)。上面代码执行完成之后, 内存中的情景如下图所示:

a3585ef220f2d35a3ca23d07dc8ffaf8.png

而下面的代码是真真正正的克隆了一个对象。

Person p = new Person(23, "zhang");

Person p1 = (Person) p.clone();

System.out.println(p);

System.out.println(p1);

从打印结果可以看出,两个对象的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量:

com.pansoft.zhangjg.testclone.Person@2f9ee1ac

com.pansoft.zhangjg.testclone.Person@67f1fba0

以上代码执行完成后, 内存中的情景如下图所示:

6b137eb1c6addf362ce6a2adb0ed8db7.png

深拷贝 or 浅拷贝

上面的示例代码中,Person中有两个成员变量,分别是name和age, name是String类型, age是int类型。代码非常简单,如下所示:

public class Person implements Cloneable{

private int age ;

private String name;

public Person(int age, String name) {

this.age = age;

this.name = name;

}

public Person() {}

public int getAge() {

return age;

}

public String getName() {

return name;

}

@Override

protected Object clone() throws CloneNotSupportedException {

return (Person)super.clone();

}

}

由于age是基本数据类型,

那么对它的拷贝没有什么疑议,直接将一个4字节的整数值拷贝过来就行。但是name是String类型的, 它只是一个引用,

指向一个真正的String对象,那么对它的拷贝有两种方式: 直接将源对象中的name的引用值拷贝给新对象的name字段,

或者是根据原Person对象中的name指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的Person对象的

name字段。这两种拷贝方式分别叫做浅拷贝和深拷贝。深拷贝和浅拷贝的原理如下图所示:

fe522af55b9885f8e5495317398f9db6.png

下面通过代码进行验证。如果两个Person对象的name的地址值相同,

说明两个对象的name都指向同一个String对象, 也就是浅拷贝, 而如果两个对象的name的地址值不同,

那么就说明指向不同的String对象, 也就是在拷贝Person对象的时候, 同时拷贝了name引用的String对象,

也就是深拷贝。验证代码如下:

Person p = new Person(23, "zhang");

Person p1 = (Person) p.clone();

String result = p.getName() == p1.getName()

? "clone是浅拷贝的" : "clone是深拷贝的";

System.out.println(result);

打印结果为:

clone是浅拷贝的

CODE_ico.pngico_fork.svg

static class Body implements Cloneable{

public Head head;

public Body() {}

public Body(Head head) {this.head = head;}

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

static class Head /*implements Cloneable*/{

public  Face face;

public Head() {}

public Head(Face face){this.face = face;}

}

public static void main(String[] args) throws CloneNotSupportedException {

Body body = new Body(new Head());

Body body1 = (Body) body.clone();

System.out.println("body == body1 : " + (body == body1) );

System.out.println("body.head == body1.head : " +  (body.head == body1.head));

}

在以上代码中, 有两个主要的类, 分别为Body和Face, 在Body类中, 组合了一个Face对象。当对Body对象进行clone时, 它组合的Face对象只进行浅拷贝。打印结果可以验证该结论:

body == body1 : false

body.head == body1.head : true

如果要使Body对象在clone时进行深拷贝, 那么就要在Body的clone方法中,将源对象引用的Head对象也clone一份。

static class Body implements Cloneable{

public Head head;

public Body() {}

public Body(Head head) {this.head = head;}

@Override

protected Object clone() throws CloneNotSupportedException {

Body newBody =  (Body) super.clone();

newBody.head = (Head) head.clone();

return newBody;

}

}

static class Head implements Cloneable{

public  Face face;

public Head() {}

public Head(Face face){this.face = face;}

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

public static void main(String[] args) throws CloneNotSupportedException {

Body body = new Body(new Head());

Body body1 = (Body) body.clone();

System.out.println("body == body1 : " + (body == body1) );

System.out.println("body.head == body1.head : " +  (body.head == body1.head));

}

打印结果为:

body == body1 : false

body.head == body1.head : false

由此可见, body和body1内的head引用指向了不同的Head对象, 也就是说在clone Body对象的同时, 也拷贝了它所引用的Head对象, 进行了深拷贝。

真的是深拷贝吗

由上一节的内容可以得出如下结论:CODE_ico.pngico_fork.svg

static class Body implements Cloneable{

public Head head;

public Body() {}

public Body(Head head) {this.head = head;}

@Override

protected Object clone() throws CloneNotSupportedException {

Body newBody =  (Body) super.clone();

newBody.head = (Head) head.clone();

return newBody;

}

}

static class Head implements Cloneable{

public  Face face;

public Head() {}

public Head(Face face){this.face = face;}

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

static class Face{}

public static void main(String[] args) throws CloneNotSupportedException {

Body body = new Body(new Head(new Face()));

Body body1 = (Body) body.clone();

System.out.println("body == body1 : " + (body == body1) );

System.out.println("body.head == body1.head : " +  (body.head == body1.head));

System.out.println("body.head.face == body1.head.face : " +  (body.head.face == body1.head.face));

}

打印结果为:

body == body1 : false

body.head == body1.head : false

body.head.face == body1.head.face : true

内存结构图如下图所示:

4f12b77e4d3f471fcddee04b4e8ebc29.png

那么,对Body对象来说,算是这算是深拷贝吗?其实应该算是深拷贝,因为对

Body对象内所引用的其他对象(目前只有Head)都进行了拷贝,也就是说两个独立的Body对象内的head引用已经指向了独立的两个Head对象。

但是,这对于两个Head对象来说,他们指向了同一个Face对象,这就说明,两个Body对象还是有一定的联系,并没有完全的独立。这应该说是一种不彻底的深拷贝。

如何进行彻底的深拷贝

对于上面的例子来说,怎样才能保证两个Body对象完全独立呢?只要在拷贝

Head对象的时候,也将Face对象拷贝一份就可以了。这需要让Face类也实现Cloneable接口,实现clone方法,并且在在Head对象的

clone方法中,拷贝它所引用的Face对象。修改的部分代码如下:

static class Head implements Cloneable{

public  Face face;

public Head() {}

public Head(Face face){this.face = face;}

@Override

protected Object clone() throws CloneNotSupportedException {

//return super.clone();

Head newHead = (Head) super.clone();

newHead.face = (Face) this.face.clone();

return newHead;

}

}

static class Face implements Cloneable{

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

再次运行上面的示例,得到的运行结果如下:

body == body1 : false

body.head == body1.head : false

body.head.face == body1.head.face : false

这说名两个Body已经完全独立了,他们间接引用的face对象已经被拷贝,也就是引用了独立的Face对象。内存结构图如下:

b837e596e3295d273c80ddb4231b8991.png

依此类推,如果Face对象还引用了其他的对象, 比如说Mouth,如果不经过处理,Body对象拷贝之后还是会通过一级一级的引用,引用到同一个Mouth对象。同理, 如果要让Body在引用链上完全独立, 只能显式的让Mouth对象也被拷贝。

到此,可以得到如下结论:如果在拷贝一个对象时,要想让这个拷贝的对象和源对象完全彼此独立,那么在引用链上的每一级对象都要被显式的拷贝。所以创

建彻底的深拷贝是非常麻烦的,尤其是在引用关系非常复杂的情况下, 或者在引用链的某一级上引用了一个第三方的对象,

而这个对象没有实现clone方法, 那么在它之后的所有引用的对象都是被共享的。

举例来说,如果被Head引用的Face类是第三方库中的类,并且没有实现Cloneable接口,那么在Face之后的所有对象都会被拷贝前后的两个

Body对象共同引用。假设Face对象内部组合了Mouth对象,并且Mouth对象内部组合了Tooth对象,

内存结构如下图:

4bdcc180e959eb9b61525850651dd0d9.png

写在最后

clone在平时项目的开发中可能用的不是很频繁,但是区分深拷贝和浅拷贝会让我

们对java内存结构和运行方式有更深的了解。至于彻底深拷贝,几乎是不可能实现的,原因已经在上一节中进行了说明。深拷贝和彻底深拷贝,在创建不可变对

象时,可能对程序有着微妙的影响,可能会决定我们创建的不可变对象是不是真的不可变。clone的一个重要的应用也是用于不可变对象的创建。关于创建不可

变对象,我会在后续的文章中进行阐述,敬请期待。

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

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

相关文章

portal认证 java_华为5700交换机通过外部开源protal和本地aaa用户认证的一些问题

各位:您好,我通过一台华为5700交换机和一台portal服务器,想利用交换机本地的aaa认证,完成用户的上网认证。配置好后,用户可以重地向到portal页面,但是认证不能通过,具体配置如下:一、…

java复制一个对象_Java中对象的复制

假如说你想复制一个简单变量。很简单:1 int n 5;2 int m n;不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。但是如果你复制的是一个对象,情况就有些复杂了。假设说我是一个beginner&#xff0c…

python判断是不是整数1002python判断是不是整数_Python判断一个数是不是为整数的方法...

Python判断一个数是不是为整数的方法发布时间:2020-07-08 15:44:30来源:亿速云阅读:84作者:清晨不懂Python判断一个数是不是为整数的方法?其实想解决这个问题也不难,下面让小编带着大家一起学习怎么去解决&…

spriteatlas 白屏的问题_Discuz白屏问题解决思

说到白屏,大家可能最先想到的就是dedecms了。Dedecms的白屏问题确实多,但是今天学习部小编要给大家介绍的是另一个大家耳熟能详的程序:discuz。说到discuz白屏,不少朋友感到非常头疼。为什么呢?Dz白屏的原因千奇百怪&a…

aix 超过一天的文件_Aix 6.1下 /dev/null 21 文件过大导致根目录爆满

原因:这是Aix6107系统下cas_agent软件的一个bug,原因是脚本写错了排查过程:1. 查看是哪个文件夹或哪个文件过大du -axg /|sort -rn|headls -ltr /dev/null*发现是 /dev/null 2>&1 过大,/dev只是存设备用的文件夹,文件都很…

HA集群实现原理 切换 JAVA_HA(一)高可用集群原理

高可用集群原理LVS集群DR模式简单的架构图如下所示:在上图的架构中,当Director服务器因软件、硬件、人为原因造成故障时,整个集群服务不可用,因此,需要再添加一台服务器实现Director服务高可用。整个系统的架构图如下所…

c语言指针没学可以学java_这是一篇来自刚脱离C语言的菜鸟所写下来的关于C语言之后转JAVA入门前期学习的感想...

/***My First writing*Name Li Tai Yue*Date 2018.12.14*/这是一篇来自刚脱离C语言的菜鸟所写下来的关于C语言之后转JAVA入门前期学习的感想。并且我也觉得这是一篇所有在校学习程序的小伙伴值得一看的文章。我想很多小伙帮都觉得敲代码是一件非常枯燥的事情,每天敲…

java outofmemory 处理_java.lang.OutOfMemoryError处理错误

原因: 常见的有以下几种:1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;3.代码中存在死循环或循环产生过多重复的对象实…

java queue 实现类 区别_Java集合11 (Queue)

java.util.Queue接口是java.util.Collection子接口。它代表一个有序的对象列表,就像List一样,但是它的使用有略微的区别。Queue被设计成从末端插入并且从头部删除的形式。Queue的实现类java.util.LinkedListjava.util.PriorityQueueLinkedList是一个非常…

实验四Java_《Java实验四》

实验4--附录三代码/*问题如下:编译如下代码,分析结果;将“注释这行”所在的行注释,再将注释的call2方法和main方法中的“StaticTest.call2(obj);”方去掉注释,再次编译,观察结果分析其原因。回答如下&#…

java是值调用_Java 只有值调用

在计算机科学中,参数传递的形式主要有以下2种: 值调用和引用调用,为了说明Java在传参过程中的参数传递方式,我们首先需要对上述中2种调用形式的定义做清晰的介绍定义现给出两者的定义:值调用(Call by Value): 指函数接收的是调用者提供的实参变量的值引用…

java 如何将数字倒置_每日一个小算法之整数中每位上的数字进行反转 20190810

题目要求:给出一个32位的有符号整数,你需要将这个整数中每位上的数字进行反转。示例 1:输入: 123输出: 321示例 2:输入: -123输出: -321示例 3:输入: 120输出: 21示例 4:输入:9646324351输出: 0注意:假设我们的环境只能存储得下 3…

mysql 投票总排行_MySQL投票表,查找每个用户对条目的最新投票,并根据值进行计数...

[编辑:]我添加了值(1052,10,3,1290839091,1)以更好地解决问题,并且必须将“item_id”添加到提取最新投票的子查询中.好极了!最后一个stackoverflow问题,我实际上可以回答!我已经浏览了一个星期寻找短暂的东西;甜蜜的我的胡同.感谢有趣的SQL问…

java多态可以传匿名对象吗_Java复习笔记2--匿名类和多态

匿名类Anonymous classes enable you to make your code more concise. They enable you to declare and instantiate a class at the same time. They are like local classes except that they do not have a name. Use them if you need to use a local class only once.pri…

java se 开发web程序_JDiy快速开发WEB之javaSE环境搭建-初级

大学的时候对web开发很感兴趣,对网页中的动画,对用户注册,对网页中表格填写等等都倍感兴趣。加之又有专业课程编程语言java,因此,对java web产生了浓厚的兴趣,再加之有北京圣思园 风中叶 大师的视频教程&am…

java apktoo_apktool.jar

apktool.jar是APKTOOL这个反编译工具必须用到的必备jar包,给大家提供最新的apktool.jar2.3.4,有需要的赶快下载吧!。相关软件软件大小版本说明下载地址apktool.jar是APKTOOL这个反编译工具必须用到的必备jar包,给大家提供最新的ap…

Java 内存 关系_JVM和Linux之间的详细内存关系

JVM和Linux之间的详细内存关系在一些具有8g物理内存的服务器上,主要运行Java服务。系统内存分配如下:Java服务的JVM堆大小设置为6g,监视过程大约需要600m,Linux本身使用大约800m。从表面上看,物理记忆应该足够&#xf…

java遍历删除原理,Java 垃圾回收机制实现原理

一、垃圾回收机制的意义Java语言中一个显著的特点就是引入了垃圾回收机制,使c程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念&…

matlab简易编程,MATLAB简单编程

本帖最后由 wanggh 于 2016-12-5 14:47 编辑用数值差分、SOR迭代法求雷诺方程和用牛顿迭代法求解轴向柱塞泵滑靴副压力场的算法,%油膜厚度场、压力场迭代 MATLAB只认弧度制,不认角度制clear all; % (60rpm1rad/s)wg1000; %1000…

php删除字段某个字段,php数如何组删除某个字段

【摘要】PHP即“超文本预处理器”,是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。下面是php数如何组删除某个字段,让我们一起…