java中的深浅克隆
假设有一个对象object,在某处又需要一个跟object一样的实例object2,强调的是object和object2是两个独立的实例,只是在开始的时候,他们是具有相同状态的(属性字段的值都相同)。遇到这种情况的做法一般是,重新new一个对象object2,将object的字段值赋予object2,即:object2=object; 这样的话两个引用仍然指向的是同一个对象,不是两个对象。
克隆方法clone()
Java中跟克隆有关的两个类分别是Cloneable接口和Object类中的clone方法,通过两者的协作来实现克隆。
首先来看看Object的clone()源代码:
/**
* Creates and returns a copy of this {@code Object}. The default
* implementation returns a so-called "shallow" copy: It creates a new
* instance of the same class and then copies the field values (including
* object references) from this instance to the new instance. A "deep" copy,
* in contrast, would also recursively clone nested objects. A subclass that
* needs to implement this kind of cloning should call {@code super.clone()}
* to create the new instance and then create deep copies of the nested,
* mutable objects.
*
* @return a copy of this object.
* @throws CloneNotSupportedException
* if this object's class does not implement the {@code
* Cloneable} interface.
*/
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class doesn't implement Cloneable");
}
return internalClone((Cloneable) this);
} /*
* Native helper method for cloning.
*/
private native Object internalClone(Cloneable o);
首先看一下java api doc中关于Cloneable接口和Object类中的clone方法的描述:
java.lang.Cloneable 接口(以下源引JavaTM 2 Platform Standard Ed. 5.0 API DOC)
此类实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。 如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException异常。
按照惯例,实现此接口的类应该使用公共方法重写 Object.clone(它是受保护的)。请参阅 Object.clone(),以获得有关重写此方法的详细信息。
注意,此接口不包含 clone 方法。因此,因为某个对象实现了此接口就克隆它是不可能的。即使 clone 方法是反射性调用的,也无法保证它将获得成功。
Cloneable接口没有任何方法,仅是个标志接口(tagging interface),若要具有克隆能力,实现Cloneable接口的类必须重写从Object继承来的clone方法,并调用Object的clone方法(见下面Object#clone的定义),重写后的方法应为public 的。
clone方法首先会判对象是否实现了Cloneable接口,若无则抛出CloneNotSupportedException, 最后会调用internalClone. intervalClone是一个native方法,一般来说native方法的执行效率高于非native方法。
当某个类要复写clone方法时,要继承Cloneable接口。通常的克隆对象都是通过super.clone()方法来克隆对象。
浅克隆(shadow clone)
克隆就是复制一个对象的复本,若只需要复制对象的字段值(对于基本数据类型,如:int,long,float等,则复制值;对于复合数据类型仅复制该字段值,如数组变量则复制地址,对于对象变量则复制对象的reference。
举个例子:
public class ShadowClone implements Cloneable{
private int a; // 基本类型
private int[] b; // 非基本类型
// 重写Object.clone()方法,并把protected改为public
@Override
public Object clone(){
ShadowClone sc = null;
try
{
sc = (ShadowClone) super.clone();
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return sc;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}
}
测试代码如下:
public class Test{
public static void main(String[] args) throws CloneNotSupportedException{
ShadowClone c1 = new ShadowClone();
//对c1赋值
c1.setA(100) ;
c1.setB(new int[]{1000}) ;
System.out.println("克隆前c1: a="+c1.getA()+" b="+c1.getB()[0]);
//克隆出对象c2,并对c2的属性A,B,C进行修改
ShadowClone c2 = (ShadowClone) c1.clone();
//对c2进行修改
c2.setA(50) ;
int []a = c2.getB() ;
a[0]=5 ;
c2.setB(a);
System.out.println("克隆前c1: a="+c1.getA()+" b="+c1.getB()[0]);
System.out.println("克隆后c2: a="+c2.getA()+ " b[0]="+c2.getB()[0]);
}
}
运行结果:
克隆前c1: a=100 b=1000
克隆前c1: a=100 b=5
克隆后c2: a=50 b[0]=5
c1和c2的对象模型:

可以看出,基本类型可以使用浅克隆,而对于引用类型,由于引用的是内容相同,所以改变c2实例对象中的属性就会影响到c1。所以引用类型需要使用深克隆。另外,在开发一个不可变类的时候,如果这个不可变类中成员有引用类型,则就需要通过深克隆来达到不可变的目的。
深克隆(deep clone)
深克隆与浅克隆的区别在于对复合数据类型的复制。若对象中的某个字段为复合类型,在克隆对象的时候,需要为该字段重新创建一个对象。
再举一个例子:
public class DeepClone implements Cloneable {
private int a; // 基本类型
private int[] b; // 非基本类型
// 重写Object.clone()方法,并把protected改为public
@Override
public Object clone(){
DeepClone sc = null;
try
{
sc = (DeepClone) super.clone();
int[] t = sc.getB();
int[] b1 = new int[t.length];
for (int i = 0; i < b1.length; i++) {
b1[i] = t[i];
}
sc.setB(b1);
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return sc;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}
}
运行结果:
克隆前c1: a=100 b=1000
克隆前c1: a=100 b=1000
克隆后c2: a=50 b[0]=5
对象模型:

使用序列化实现深克隆
public class DeepClone implements Serializable{
private int a;
private int[] b;
public int getA() {
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}
}
然后编写测试类:
package test2; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Test2{
public static void main(String[] args) throws CloneNotSupportedException{
Test2 t = new Test2();
DeepClone dc1 = new DeepClone();
// 对dc1赋值
dc1.setA(100);
dc1.setB(new int[] { 1000 });
System.out.println("克隆前dc1: a=" + dc1.getA()+"b[0]=" + dc1.getB()[0]);
DeepClone dc2 = (DeepClone) t.deepClone(dc1);
// 对c2进行修改
dc2.setA(50);
int[] a = dc2.getB();
a[0] = 500;
System.out.println("克隆后dc1: a=" + dc1.getA()+"b[0]=" + dc1.getB()[0]);
System.out.println("克隆后dc2: a=" + dc2.getA()+"b[0]=" + dc2.getB()[0]);
}
// 用序列化与反序列化实现深克隆
public Object deepClone(Object src){
Object o = null;
try{
if (src != null){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(src);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
o = ois.readObject();
ois.close();
}
} catch (IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return o;
}
}
运行后的结果如下:
克隆前dc1: a=100 b[0]=1000
克隆后dc1: a=100 b[0]=1000
克隆后dc2: a=50 b[0]=500
可以看到,两个引用所指向的对象在堆中相互独立,互不干扰,这样就实现了深度克隆。
总结:
1、克隆方法用于创建对象的拷贝,为了使用clone方法,类必须实现java.lang.Cloneable接口重写protected方法clone,如果没有实现Clonebale接口会抛出CloneNotSupportedException.
2、在克隆java对象的时候不会调用构造器
3、java提供一种叫浅拷贝(shallow copy)的默认方式实现clone,创建好对象的副本后然后通过赋值拷贝内容,意味着如果你的类包含引用类型,那么原始对象和克隆都将指向相同的引用内容,这是很危险的,因为发生在可变的字段上任何改变将反应到他们所引用的共同内容上。为了避免这种情况,需要对引用的内容进行深度克隆。
4、按照约定,实例的克隆应该通过调用super.clone()获取,这样有助克隆对象的不变性。如:clone!=original和clone.getClass()==original.getClass(),尽管这些不是必须的
参考资料
http://www.cnblogs.com/shishm/archive/2011/10/10/2205743.html
http://blog.csdn.net/bigconvience/article/details/25025561
java中的深浅克隆的更多相关文章
- 深入浅出Java中的clone克隆方法,写得太棒了!
作者:张纪刚 blog.csdn.net/zhangjg_blog/article/details/18369201/ Java中对象的创建 clone 顾名思义就是 复制 , 在Java语言中, c ...
- Java中的深浅拷贝问题,你清楚吗?
一.前言 拷贝这个词想必大家都很熟悉,在工作中经常需要拷贝一份文件作为副本.拷贝的好处也很明显,相较于新建来说,可以节省很大的工作量.在Java中,同样存在拷贝这个概念,拷贝的意义也是可以节省创建对象 ...
- JAVA中对象的克隆及深拷贝和浅拷贝
使用场景: 在日常的编程过程 中,经常会遇到,有一个对象OA,在某一时间点OA中已经包含了一些有效值 ,此时可能会需一个和OA完全相对的新对象OB,并且要在后面的操作中对OB的任何改动都不会影响到OA ...
- java中浅层克隆和深层克隆
1.浅复制与深复制概念 浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象 ...
- Java提高篇——对象克隆(复制)
假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short, ...
- Java中的深拷贝和浅拷贝
1.浅拷贝与深拷贝概念 (1)浅拷贝(浅克隆) 浅拷贝又叫浅复制,将对象中的所有字段复制到新的对象(副本)中.其中,值类型字段(java中8中原始类型)的值被复制到副本中后,在副本中的修改不会影响到源 ...
- Java中对象的深复制和浅复制详解
1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象. ⑵ ...
- Java中的深复制与浅复制
1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所考虑的对象,而不 复制它所引用的对象. ...
- JAVA中浅复制与深复制 - coolmist - ITeye技术网站
body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...
随机推荐
- SSH 公私钥的基本使用
SSH 公私钥的基本使用 创建密钥 使用 ssh-keygen 生成公私钥 在终端敲入 ssh-keygen 命令,一路一直按回车下去,会把密钥文件放置在默认路径,也就是 ~/.ssh/ 路径下,并且 ...
- CodeForcs 797E Array Queries
$dp$预处理,暴力. 如果$k > sqrt(n)$,那么答案不会超过$sqrt(n)$,暴力模拟即可.如果$k <= sqrt(n)$,那么可以$dp$预处理打表. #include ...
- LaTeX算法排版 笔记
方式一 需要包含的 \usepackage[noend]{algpseudocode} \usepackage{algorithmicx,algorithm} 源码 \begin{algorithm} ...
- mybatis递归,一对多代码示例
今天需要做一个功能,根据专业,有不同的章节,章节下面有对应的习题, 由于只有这么两级,可以不用使用递归,直接查询父集,之后foreach查询子集放入对应的list集合. 虽然实现了,感觉毕竟,太low ...
- RxSwift 系列(三)
RxSwift 系列(三) -- Combination Operators 前言 本篇文章将要学习如何将多个Observables组合成一个Observable.Combination Operat ...
- HTTP状态码,400,404,500,503
HTTP状态码(HTTP Status Code) 一些常见的状态码为: 200 - 服务器成功返回网页 400 服务器不理解请求的语法 404 - 请求的网页不存在 503 - 服务不可用 所有状态 ...
- asp.net mvc中DropDownList
asp.net mvc中DropDownList的使用. 下拉列表框 以分为两个部分组成:下拉列表和默认选项 DropDownList扩展方法的各个重载版本基本上都会传递到这个方法上: publi ...
- hdu 2795 Billboard 线段树单点更新
Billboard Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=279 ...
- 将DLL挂接到远程进程之中(远程注入)
线程的远程注入 要实现线程的远程注入必须使用Windows提供的CreateRemoteThread函数来创建一个远程线程该函数的原型如下:HANDLE CreateRemoteThread( ...
- XY8782S00 BL-W8782 BL-R8782MS1 SDIO接口 高性能、低功耗、体积小 wifi无线模块
1.产品简介 BL-8782是一款高性能.低功耗.体积小SDIO接口无线模组,符合IEEE802.11N标准,并向下兼容IEEE802.11B/G标准,支持IEEE 802.11i安全协议,以及IEE ...