java 深拷贝与浅拷贝机制详解
概要:
在Java中,拷贝分为深拷贝和浅拷贝两种。java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝。
(一)Object中clone方法
如果我们new出一个新对象,用一个声明去引用它,之后又用另一个声明去引用前一个声明,那么最后的结果是:这两个声明的变量将指向同一个对象,一处被改全部被改。如果我们想创建一个对象的copy,这个copy和对象的各种属性完全相同,而且修改这个copy和原对象毫无关系,那么这个时候我们就要用到clone方法。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package Clone;import java.util.Date;/** * * @author QuinnNorris * java中的两种拷贝机制 */public class Clone { /** * @param args * @throws CloneNotSupportedException */ public static void main(String[] args) throws CloneNotSupportedException { // TODO Auto-generated method stub ClassA valA = new ClassA(1, "old", new Date()); // 声明一个新的ClassA对象,我们不需要太关注ClassA的功能 ClassA valB = valA; // 将valA引用的对象赋给valB valA.setObject("new"); // 更改valA中的值,此时valB也被更改了,因为valA和valB指向同一个对象 valB = valA.clone();//通过clone方法制造副本 }} |
ClassA类中关于clone方法的重写部分:
|
1
2
3
4
5
6
7
8
|
//需要实现Cloneable接口public class ClassA implements Cloneable { public ClassA clone() throws CloneNotSupportedException { return (ClassA) super.clone();//调用父类(Object)的clone方法 }} |
1.如何使用Object中clone方法的
有人总结使用clone方法的四条法则,我们一起分享一下:
- 为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
- 在派生类中覆盖基类的clone()方法,并声明为public。
- 在派生类的clone()方法中,调用super.clone()。
- 在派生类中实现Cloneable接口。
2.protected修饰的clone方法
在java.lang.Object的中,他将clone方法设置为protected修饰,这是很特殊的一种情况。protected的作用域是:包可见+可继承。之所以这样设置,是因为这个方法要返回的是克隆出来的对象,即clone方法要去克隆的类型是未知的,没有办法确定返回值的类型,自然只能让子孙后代来实现它重写它,为了能够让后代继承而又不过与张开,设置为了protected类型。
3.实现clone方法需要实现Cloneable接口
那么我们重写clone方法的时候为什么要去实现Cloneable接口呢?事实上,Cloneable接口是java中的一个标记接口,标记接口是指那些没有方法和属性的接口,他们存在只是为了让大家知道一些信息,而且在用:xxx instanceof Cloneable 的时候可以进行判断。Cloneable这个接口的出现就是为了让设计者知道要进行克隆处理了。如果一个对象需要克隆,但是没有实现(实际上,这里的“实现”换成“写上”更准确)Cloneable接口,那么会产生一个已检验异常。
4.实现clone方法需要调用父类的clone
我们为了达到复制一个和调用方法的这个对象一模一样的对象的目的,我们需要使用父类的clone方法,父类也以此类推,知道达到了Object的clone方法,那么Object的clone方法有什么用呢?API中是这样说的:
protected Object clone( ) throws CloneNotSupportedException
创建并返回此对象的一个副本。
“副本”的准确含义可能依赖于对象的类。这样做的目的是,对于任何对象 x,
表达式: x.clone() != x为 true,
表达式: x.clone().getClass() == x.getClass()也为 true,
但这些并非必须要满足的要求。
一般情况下:
x.clone().equals(x)为 true,但这并非必须要满足的要求。
按照惯例,返回的对象应该通过调用 super.clone 获得。
如果一个类及其所有的超类(Object 除外)都遵守此约定,则 x.clone().getClass() == x.getClass()。
上面就是API中对clone的一部分基本讲解。我们可以得出结论的是,只要合理的调用了spuer.clone( )它就会返回一个被克隆的对象。在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。在这个克隆对象中,所有的属性都和被克隆的对象的属性相同,而这些相同的属性分为两种:
第一种 : 八大原始类型和不可变的对象(比如String)
第二种 : 其他类对象
对于第一种,clone方法将他们的值设置为原对象的值,没有任何问题。对于第二种,clone方法只是简单的将复制的新对象的引用指向原对象指向的引用,第二种的类对象会被两个对象修改。那么这个时候就涉及一个深浅拷贝的概念了。
(二)浅拷贝
浅拷贝:被拷贝对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
比如举个例子,一个类A中有另外一个类B类型的变量。在A重写clone函数调用super.clone的时候,创建的新对象和原来对象中的类B类型的变量是同一个,他们指向了同一个B的类型变量。如果在A中对B的变量做了修改,在新的拷贝出来的对象中B的变量也会被同样的修改。
请记住,直接调用super.clone实现的clone方法全部都是浅拷贝。
(三)深拷贝
深拷贝:被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
通俗的说,如果说浅拷贝,开始的时候是两条线,如果在最后有一个其他类的变量,那么这两条线最后会合二为一,共同指向这变量,都能对他进行操作。深拷贝则是完完全全的两条线,互不干涉,因为他已经把所有的内部中的变量的对象全都复制一遍了。
深拷贝在代码中,需要在clone方法中多书写调用这个类中其他类的变量的clone函数。
(四)串行化深拷贝
在框架中,有的时候我们发现其中并没有重写clone方法,那么我们在需要拷贝一个对象的时候是如何去操作的呢?答案是我们经常会使用串行化方法,实现Serializable接口。
去寻找其他的方法来替代深拷贝也是无可奈何的事情,如果采用传统的深拷贝,难道你拷贝一个对象的时候向其中追无数层来拷贝完所有的对象变量么?先不谈这么做的时间消耗,仅仅是写这样的代码都会让人望而生畏。串行化深拷贝就是这样一个相对简单的方法。
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
上面是网上的专业解释,我也不在这里班门弄斧了。在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
|
1
2
3
4
5
6
7
8
9
10
11
|
public Object deepClone() { //写入对象 ByteArrayOutoutStream bo=new ByteArrayOutputStream(); ObjectOutputStream oo=new ObjectOutputStream(bo); oo.writeObject(this); //读取对象 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi=new ObjectInputStream(bi); return(oi.readObject()); } |
虽然这种学院派的代码看起来很复杂,其实只是把对象放到流里,再拿出来。相比较分析判断无数的clone,这样简直是再简单不过了。这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象是否设成transient。
transient:一个对象只要实现了Serilizable接口,这个对象就可以被序列化(序列化是指将java代码以字节序列的形式写出,即我们上面代码前三行写入对象),Java的这种序列化模式为开发者提供了很多便利,可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个的所有属性和方法都会自动序列化。但是有种情况是有些属性是不需要序列号的,所以就用到这个关键字。只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
java 深拷贝与浅拷贝机制详解 --摘自-https://www.jb51.net/article/106088.htm
java 深拷贝与浅拷贝机制详解的更多相关文章
- 【转】java的动态代理机制详解
java的动态代理机制详解 在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们 ...
- java的动态代理机制详解-----https://www.cnblogs.com/xiaoluo501395377/p/3383130.html
java的动态代理机制详解-----https://www.cnblogs.com/xiaoluo501395377/p/3383130.html
- JAVA中的GC机制详解
优秀Java程序员必须了解的GC工作原理 一个优秀的Java程序员必须了解GC的工作原理.如何优化GC的性能.如何与GC进行有限的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只 ...
- Java并发之Synchronized机制详解
带着问题阅读 1.Synchronized如何使用,加锁的粒度分别是什么 2.Synchronized的实现机制是什么 3.Synchronized是公平锁吗 4.Java对Synchronized做 ...
- java的动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- Java的动态代理机制详解(转)
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- (转)java的动态代理机制详解
原文出自:http://www.cnblogs.com/xiaoluo501395377/p/3383130.html 在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一 ...
- [转载] java的动态代理机制详解
转载自http://www.cnblogs.com/xiaoluo501395377/p/3383130.html 代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代 ...
- Java虚拟机:类加载机制详解
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 大家知道,我们的Java程序被编译器编译成class文件,在class文件中描述的各种信息,最终都需要加载到虚拟机内存才能运行和使用,那么 ...
随机推荐
- 课堂笔记【java JDBC】
目录 JDBC简介 工作原理: 工作过程: JDBC驱动与连接 JDBC驱动 连接JDBC驱动 1.下载特定数据库的JDBCjar包 2.加载并注册数据库驱动 3.连接驱动 JDBC常见API JDB ...
- Linu之用户管理【useradd】【userdel】【usermod】【passwd】【权限】
linux下创建用户 1.用户的创建 • 简介 linux是一个多用户多任务的分时操作系统,每个用户都是在root下的一个子用户,拥有不同的权限.用户登入成功后可进入系统和自己的主目录. •实现账号的 ...
- Python正则表达式re.findall一个有趣的现象
下面通过几个案例来分析一下, 注意:本节的parsematch函数请参考<妙用re.sub分析正则表达式解析匹配过程> 案例一: >>> re.findall(r&quo ...
- 第14.9节 Python中使用urllib.request+BeautifulSoup获取url访问的基本信息
利用urllib.request读取url文档的内容并使用BeautifulSoup解析后,可以通过一些基本的BeautifulSoup对象输出html文档的基本信息.以博文<第14.6节 使用 ...
- PyQt(Python+Qt)学习随笔:QAbstractItemView的showDropIndicator属性
老猿Python博文目录 老猿Python博客地址 概述 QAbstractItemView的showDropIndicator属性用于控制在拖拽过程中显示当前拖拽到的位置,当释放时则在当前拖拽位置覆 ...
- Eclipse配置反编译
Eclipse配置反编译 之前用IDEA一直让我很喜欢的点就是,什么东西都自动集成,下载.但是终归是学(po)习(jie)版,在正式企业开发中,要小心版权的问题(公司给你买了当我没说).抛开插件能 ...
- Day1-7【Scrum 冲刺博客集合】
Day1-Day7博客链接 Day1[Scrum 冲刺博客] Day2[Scrum 冲刺博客] Day3[Scrum 冲刺博客] Day4[Scrum 冲刺博客] Day5[Scrum 冲刺博客] D ...
- Scrum 冲刺第六天
一.每日站立式会议 1.会议内容 1)进行每日工作汇报 张博愉: 昨天已完成的工作:学习如何编写用户手册 今日工作计划:编写测试计划 工作中遇到的困难:文档不知如何动手 张润柏: 昨天已完成的工作:完 ...
- Java中的Reference类使用
Java 2 平台引入了 java.lang.ref 包,这个包下面包含了几个Reference相关的类,Reference相关类将Java中的引用也映射成一个对象,这些类还提供了与垃圾收集器(gar ...
- git相关操作
git相关命令 基本操作 git init git add xxx git commit -m "first commit" git tag -a V1.0 -m '我的标签' g ...