文档章节

java浅克隆与深克隆

h
 hay_lee
发布于 2016/08/29 11:40
字数 1720
阅读 3
收藏 0

如果我们有一个对象a,我们想得到它的一个克隆,那么我们该怎么做呢?最直观、最笨的方法是我们先new一个a的同类对象b,然后挨个拷贝a的属性给b中的相应属性。那么,这里马上就得引出两个概念:浅克隆与深克隆。

如果用直白的、非严格定义的语言来解释这两个概念,那么可以这么说:

所谓浅克隆是指复制一个对象的实例,但是这个对象中包含的其它的对象还是共用的。

所谓深克隆是指复制一个对象的实例,而且这个对象中包含的其它的对象也要复制一份。

如果我们要深克隆一个对象,而这个对象又比较复杂,它还包含n多其它对象的引用,那么要用我们开始说的那种克隆方法,简直是要人命! 即便是我们只要一个浅克隆的对象,如果这个对象有几十上百个基本属性,我们挨个去复制也是不可接受的!

那么,我们先来看看Java中浅克隆一个对象可以怎么做。

Object 类提供有一个clone方法,它的方法签名如下:

 

[java] view plain copy

  1. protected native Object clone() throws CloneNotSupportedException;  


可以看到:(1)它是一个native方法。native方法的效率一般来说都远高于非native方法。

 

                    (2)它是一个protected方法。

                    (3)它返回一个Object。

如果我们要在其它类中调用clone方法,那么我们就要重写这个方法,将它的方法属性改为public 。这个方法其实就提供了浅克隆的功能。

验证的代码如下:

Swallow.Java:

 

[java] view plain copy

  1. package com.myclone.test;  
  2.   
  3.   
  4. public class Swallow implements Cloneable{  
  5.   
  6.   
  7.     private String name;  
  8.     private Wing leftWing;  
  9.     private Wing rightWing;  
  10.       
  11.     public Swallow(String name, Wing leftWing, Wing rightWing){  
  12.         this.name = name;  
  13.         this.leftWing = leftWing;  
  14.         this.rightWing = rightWing;  
  15.     }  
  16.   
  17.     public String getName() {  
  18.         return name;  
  19.     }  
  20.   
  21.     public void setName(String name) {  
  22.         this.name = name;  
  23.     }  
  24.   
  25.     public Wing getLeftWing() {  
  26.         return leftWing;  
  27.     }  
  28.   
  29.     public void setLeftWing(Wing leftWing) {  
  30.         this.leftWing = leftWing;  
  31.     }  
  32.   
  33.     public Wing getRightWing() {  
  34.         return rightWing;  
  35.     }  
  36.   
  37.     public void setRightWing(Wing rightWing) {  
  38.         this.rightWing = rightWing;  
  39.     }  
  40.       
  41.     @Override  
  42.     protected Object clone() throws CloneNotSupportedException {  
  43.         // TODO 自动生成的方法存根  
  44.         return super.clone();  
  45.     }  
  46. }  


Wing.java:

 

 

[java] view plain copy

  1. package com.myclone.test;  
  2.   
  3. public class Wing{  
  4.   
  5.     private int width;  
  6.   
  7.     public Wing(int width){  
  8.         this.width = width;  
  9.     }  
  10.       
  11.     public int getWidth() {  
  12.         return width;  
  13.     }  
  14.   
  15.     public void setWidth(int width) {  
  16.         this.width = width;  
  17.     }  
  18. }  


Main.java:

 

 

[java] view plain copy

  1. package com.myclone.test;  
  2.   
  3. public class Main {  
  4.   
  5.     public static void main(String[] args) throws CloneNotSupportedException {  
  6.         // TODO 自动生成的方法存根  
  7.   
  8.         Swallow s1 = new Swallow("大雁a", new Wing(10), new Wing(10));  
  9.         Swallow s2 = (Swallow) s1.clone();  
  10.           
  11.         System.out.println("(s1==s2)="+ (s1==s2));  
  12.         System.out.println("s1.name="+ s1.getName()+", s2.name="+s2.getName());  
  13.         System.out.println("(s1.leftWing==s2.leftWing) = "+(s1.getLeftWing()==s2.getLeftWing()));  
  14.         System.out.println("(s1.rightWing==s2.rightWing) = "+(s1.getRightWing()==s2.getRightWing()));      
  15.     }  
  16. }  


运行结果:

 

(s1==s2)=false
s1.name=大雁a, s2.name=大雁a
(s1.leftWing==s2.leftWing) = true
(s1.rightWing==s2.rightWing) = true

从运行结果中我们可以看出,调用 s1.clone() 方法我们得到的是另一个对象,它浅克隆了s1,因为s1和s2中的对象属性 leftWing 指向的是同一个实例,rightWing指向的是同一个实例。注意:要调用clone方法,必须要实现 Cloneable接口,在我们这个例子中即Swallow实现了Cloneable接口。

 

如果我们需要深克隆的对象呢?首先我们想到的是,可以再次重写Swallow中的clone方法,将leftWing 和 rightWing 克隆一份。那么Wing 也要重写它的clone方法,改写后的Wing类:

 

[java] view plain copy

  1. package com.myclone.test;  
  2.   
  3. public class Wing implements Cloneable{  
  4.   
  5.     private int width;  
  6.   
  7.     public Wing(int width){  
  8.         this.width = width;  
  9.     }  
  10.       
  11.     public int getWidth() {  
  12.         return width;  
  13.     }  
  14.   
  15.     public void setWidth(int width) {  
  16.         this.width = width;  
  17.     }  
  18.       
  19.     @Override  
  20.     protected Object clone() throws CloneNotSupportedException {  
  21.         // TODO 自动生成的方法存根  
  22.         return super.clone();  
  23.     }  
  24. }  


改写后的Swallow类:

 

 

[java] view plain copy

  1. package com.myclone.test;  
  2.   
  3.   
  4. public class Swallow implements Cloneable{  
  5.   
  6.   
  7.     private String name;  
  8.     private Wing leftWing;  
  9.     private Wing rightWing;  
  10.       
  11.     public Swallow(String name, Wing leftWing, Wing rightWing){  
  12.         this.name = name;  
  13.         this.leftWing = leftWing;  
  14.         this.rightWing = rightWing;  
  15.     }  
  16.   
  17.     public String getName() {  
  18.         return name;  
  19.     }  
  20.   
  21.     public void setName(String name) {  
  22.         this.name = name;  
  23.     }  
  24.   
  25.     public Wing getLeftWing() {  
  26.         return leftWing;  
  27.     }  
  28.   
  29.     public void setLeftWing(Wing leftWing) {  
  30.         this.leftWing = leftWing;  
  31.     }  
  32.   
  33.     public Wing getRightWing() {  
  34.         return rightWing;  
  35.     }  
  36.   
  37.     public void setRightWing(Wing rightWing) {  
  38.         this.rightWing = rightWing;  
  39.     }  
  40.       
  41. //  @Override  
  42. //  protected Object clone() throws CloneNotSupportedException {  
  43. //      // TODO 自动生成的方法存根  
  44. //      return super.clone();  
  45. //  }  
  46.       
  47.     @Override  
  48.     protected Object clone() throws CloneNotSupportedException {  
  49.         // TODO 自动生成的方法存根  
  50.         Swallow swallow = (Swallow) super.clone();  
  51.         Wing lefWing = (Wing) swallow.getLeftWing().clone();  
  52.         Wing rightWing = (Wing) swallow.getRightWing().clone();  
  53.         swallow.setLeftWing(lefWing);  
  54.         swallow.setRightWing(rightWing);  
  55.         return swallow;  
  56.     }  
  57. }  


再次运行Main,得到的结果是:

 

(s1==s2)=false
s1.name=大雁a, s2.name=大雁a
(s1.leftWing==s2.leftWing) = false
(s1.rightWing==s2.rightWing) = false

可以看出s2已经是s1的深克隆了。这种方法有个缺点就是,如果Wing还含有对象属性的话,那么我们就必须依次去重写属性类的clone方法!

另一种方法是通过序列化和反序列化来实现深克隆。这种方法实际上就是“先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流中,再从流中读出来,便可以重建对象。” ——java.lang.Object.clone()分析

要注意的是所有相关的类都要实现Serializable接口,改写后的实例:

Wing.java

 

[java] view plain copy

  1. package com.myclone.test;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class Wing implements Cloneable, Serializable{  
  6.   
  7.     /** 
  8.      *  
  9.      */  
  10.     private static final long serialVersionUID = -6757262538695878009L;  
  11.     private int width;  
  12.   
  13.     public Wing(int width){  
  14.         this.width = width;  
  15.     }  
  16.       
  17.     public int getWidth() {  
  18.         return width;  
  19.     }  
  20.   
  21.     public void setWidth(int width) {  
  22.         this.width = width;  
  23.     }  
  24.       
  25.     @Override  
  26.     protected Object clone() throws CloneNotSupportedException {  
  27.         // TODO 自动生成的方法存根  
  28.         return super.clone();  
  29.     }  
  30. }  


Swallow.java

 

 

[java] view plain copy

  1. package com.myclone.test;  
  2.   
  3. import java.io.ByteArrayInputStream;  
  4. import java.io.ByteArrayOutputStream;  
  5. import java.io.IOException;  
  6. import java.io.ObjectInputStream;  
  7. import java.io.ObjectOutputStream;  
  8. import java.io.Serializable;  
  9.   
  10.   
  11. public class Swallow implements Cloneable, Serializable{  
  12.   
  13.   
  14.     /** 
  15.      *  
  16.      */  
  17.     private static final long serialVersionUID = -2152309743441152834L;  
  18.     private String name;  
  19.     private Wing leftWing;  
  20.     private Wing rightWing;  
  21.       
  22.     public Swallow(String name, Wing leftWing, Wing rightWing){  
  23.         this.name = name;  
  24.         this.leftWing = leftWing;  
  25.         this.rightWing = rightWing;  
  26.     }  
  27.   
  28.     public String getName() {  
  29.         return name;  
  30.     }  
  31.   
  32.     public void setName(String name) {  
  33.         this.name = name;  
  34.     }  
  35.   
  36.     public Wing getLeftWing() {  
  37.         return leftWing;  
  38.     }  
  39.   
  40.     public void setLeftWing(Wing leftWing) {  
  41.         this.leftWing = leftWing;  
  42.     }  
  43.   
  44.     public Wing getRightWing() {  
  45.         return rightWing;  
  46.     }  
  47.   
  48.     public void setRightWing(Wing rightWing) {  
  49.         this.rightWing = rightWing;  
  50.     }  
  51.       
  52. //  @Override  
  53. //  protected Object clone() throws CloneNotSupportedException {  
  54. //      // TODO 自动生成的方法存根  
  55. //      return super.clone();  
  56. //  }  
  57.       
  58.     @Override  
  59.     protected Object clone() throws CloneNotSupportedException {  
  60.         // TODO 自动生成的方法存根  
  61.         Swallow swallow = (Swallow) super.clone();  
  62.         Wing lefWing = (Wing) swallow.getLeftWing().clone();  
  63.         Wing rightWing = (Wing) swallow.getRightWing().clone();  
  64.         swallow.setLeftWing(lefWing);  
  65.         swallow.setRightWing(rightWing);  
  66.         return swallow;  
  67.     }  
  68.       
  69.     /** 
  70.      * 深克隆 
  71.      * @return 
  72.      * @throws IOException 
  73.      * @throws ClassNotFoundException 
  74.      */  
  75.     public Object deepClone() throws IOException, ClassNotFoundException{  
  76.         ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  77.         ObjectOutputStream oos = new ObjectOutputStream(baos);  
  78.         oos.writeObject(this);  
  79.           
  80.         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());  
  81.         ObjectInputStream ois = new ObjectInputStream(bais);  
  82.         return ois.readObject();  
  83.     }  
  84. }  


Main.java:

 

 

[java] view plain copy

  1. package com.myclone.test;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. public class Main {  
  6.   
  7.     public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException {  
  8.         // TODO 自动生成的方法存根  
  9.   
  10.         Swallow s1 = new Swallow("大雁a", new Wing(10), new Wing(10));  
  11.         //Swallow s2 = (Swallow) s1.clone();  
  12.         Swallow s2 = (Swallow) s1.deepClone();  
  13.           
  14.         System.out.println("(s1==s2)="+ (s1==s2));  
  15.         System.out.println("s1.name="+ s1.getName()+", s2.name="+s2.getName());  
  16.         System.out.println("(s1.leftWing==s2.leftWing) = "+(s1.getLeftWing()==s2.getLeftWing()));  
  17.         System.out.println("(s1.rightWing==s2.rightWing) = "+(s1.getRightWing()==s2.getRightWing()));      
  18.     }  
  19. }  


运行结果:

 

(s1==s2)=false
s1.name=大雁a, s2.name=大雁a
(s1.leftWing==s2.leftWing) = false
(s1.rightWing==s2.rightWing) = false

 

由此可见,通过序列化和反序列化来实现深克隆是最优雅、最方便的。

2015.09.02  补充:

上午有个同事说深克隆还有一个方法,就是用json,这的确是一个好方法!那么,我们可以在Swallow 类中再加一个deepClone2方法,效果是一样的:

 

[java] view plain copy

  1. public Swallow deepClone2(){  
  2.         Gson gson = new Gson();  
  3.         String json = gson.toJson(this);  
  4.         return gson.fromJson(json, this.getClass());  
  5.     }  

似乎更简单一些~ 不过当时我就在想这样效率是否会低一些,测试了下10万次克隆,结果如下:

 

deepClone 方法耗时=1564
deepClone2 方法耗时=4029

本文转载自:

h
粉丝 0
博文 6
码字总数 0
作品 0
西安
程序员
私信 提问
Java拾遗:008 - 对象克隆与浅拷贝、深拷贝

对象克隆 Object类中有一个方法叫,完整代码 首先它是一个Native方法,而且是受保护的(),抛出一个异常(JDK1.8)。 通常程序员自己定义的类不能直接调用方法,如果要在外部调用,需要重写...

一别丶经年
2018/08/04
59
0
Java 克隆Cloneable

Java 克隆包括 浅克隆和深克隆 以克隆羊剖析 Java 克隆 http://cn.honoit.com/home/?com=detail&id=11113279640976

ThimothyJose
2016/07/18
187
0
Java中如何克隆集合——ArrayList和HashSet深拷贝

编程人员经常误用各个集合类提供的拷贝构造函数作为克隆,,,或者其他集合实现的方法。需要记住的是,Java集合的拷贝构造函数只提供浅拷贝而不是深拷贝,这意味着存储在原始List和克隆List中...

markGao
2014/04/18
1K
0
Java中如何克隆集合——ArrayList和HashSet深拷贝

编程人员经常误用各个集合类提供的拷贝构造函数作为克隆List,Set,ArrayList,HashSet或者其他集合实现的方法。需要记住的是,Java集合的拷贝构造函数只提供浅拷贝而不是深拷贝,这意味着存...

LCZ777
2014/04/14
89
0
Java中的克隆Cloneable

浅克隆与深克隆 当拷贝一个变量时,原始变量与拷贝变量引用了同一个对象。那么当改变一个变量所引用的对象时,就会对另一个变量产生影响。形象化一点说,就像某人A有一把遥控,用来控制电视,...

静水楼台
2013/06/04
219
0

没有更多内容

加载失败,请刷新页面

加载更多

zk服务预启动和启动选举过程

QuorumPeerMain类public static void main(String[] args) { QuorumPeerMain main = new QuorumPeerMain(); try { main.initializeAndRun(args); } catch (Illega......

writeademo
31分钟前
6
0
深究递归和迭代的区别、联系、优缺点及实例对比

http://blog.csdn.net/laoyang360/article/details/7855860 http://www.zhihu.com/question/20278387 深究递归和迭代的区别、联系、优缺点及实例对比 1.概念区分 递归的基本概念:程序调用自身...

slagga
33分钟前
5
0
基于SOM-TL6678核心板而研发的TL6678-EasyEVM开发板硬件说明书

TL6678-EasyEVM是广州创龙基于SOM-TL6678核心板而研发的一款多核高性能DSP开发板。开发板采用核心板+底板方式,底板采用沉金无铅工艺的四层板设计,尺寸为200mm*106.65mm,它为用户提供了SOM...

Tronlong创龙
37分钟前
4
0
分别在有网和无网状态下批量安装python库

## 连网状态下,批量安装python库查看当前python环境下已安装的库包版本查看命令:pip freeze"""alabaster==0.7.10anaconda-client==1.6.3anaconda-navigator==1.6.2anaconda-projec......

KYO4321
40分钟前
3
0
fiddler、Charles-断点

一、添加Charles断点 1、用Charles抓包发起一次接口请求 2、对要打断点的接口右键,选择【Breakpoints】 二、Charles断点设置 1、点击Charles菜单-【Proxy】-【Breakpoint...】,此时会弹出【...

果树啊
40分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部