关闭

java浅克隆与深克隆

116人阅读 评论(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

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:172003次
    • 积分:2281
    • 等级:
    • 排名:第16495名
    • 原创:19篇
    • 转载:249篇
    • 译文:0篇
    • 评论:5条
    文章分类
    最新评论