分享

简单克隆和深度克隆

 9loong 2008-12-30
 
一个羊圈,里面有一群羊。

简单克隆:
你重新建了一个羊圈,然后把那群羊圈进去,此时有2个羊圈,但里面是相同的一群羊。


深度克隆:
你重新建了一个羊圈,然后把每只羊也克隆了一份(医学技术高啊),此时有2个羊圈,且有2群羊。
每只羊在另一个圈里都有一个完全相同的羊。



如果能把草地、地球都克隆了,就更强了。
 

java clone方法使用详解

Java语言的一个优点就是取消了指针的概念,但也导致了许多程序员在编程中常常忽略了对象与引用的区别,特别是先学c、c++后学java的程序员。并且由于Java不能通过简单的赋值来解决对象复制的问题,在开发过程中,也常常要要应用clone()方法来复制对象。比如函数参数类型是自定义的类时,此时便是引用传递而不是值传递。以下是一个小例子:

Java代码 复制代码
  1. public class A {   
  2.     public String name;   
  3. }  
Java代码 复制代码
  1. public class testClone {   
  2.        
  3.     public void changeA(A a){   
  4.         a.name="b";   
  5.     }   
  6.     public void changInt(int i){   
  7.         i=i*2+100;   
  8.     }   
  9.        
  10.     /**  
  11.      * @param args  
  12.      */  
  13.     public static void main(String[] args) {   
  14.         // TODO Auto-generated method stub   
  15.         testClone test=new testClone();   
  16.         A a=new A();   
  17.         a.name="a";   
  18.         System.out.println("before change : a.name="+a.name);   
  19.         test.changeA(a);   
  20.         System.out.println("after  change : a.name="+a.name);   
  21.         int i=1;   
  22.         System.out.println("before change : i="+i);   
  23.         test.changInt(i);   
  24.         System.out.println("after  change : i="+i);   
  25.     }   
  26.   
  27. }  



此时输出的结果是:

Java代码 复制代码
  1. before change : a.name=a   
  2. after  change : a.name=b   
  3. before change : i=1  
  4. after  change : i=1  

从这个例子知道Java对对象和基本的数据类型的处理是不一样的。在Java中用对象的作为入口参数的传递则缺省为"引用传递",也就是说仅仅传递了对象的一个"引用",这个"引用"的概念同C语言中的指针引用是一样的。当函数体内部对输入变量改变时,实质上就是在对这个对象的直接操作。
除了在函数传值的时候是"引用传递",在任何用"="向对象变量赋值的时候都是"引用传递",如:
Java代码 复制代码
  1. A a1=new A();   
  2. A a2=new A();   
  3. a1.name="a1";   
  4. a2=a1;   
  5. a2.name="a2";   
  6. System.out.println("a1.name="+a1.name);   
  7. System.out.println("a2.name="+a2.name);  


此时输出的结果是:

Java代码 复制代码
  1. a1.name=a2   
  2. a2.name=a2  



如果我们要用a2保存a1对象的数据,但又不希望a2对象数据被改变时不影响到a1。实现clone()方法是其一种最简单,也是最高效的手段。
下面我们来实现A的clone方法

Java代码 复制代码
  1. public class A implements Cloneable {   
  2.     public String name;   
  3.   
  4.     public Object clone() {   
  5.         A o = null;   
  6.         try {   
  7.             o = (A) super.clone();   
  8.         } catch (CloneNotSupportedException e) {   
  9.             e.printStackTrace();   
  10.         }   
  11.         return o;   
  12.     }   
  13.   
  14. }  

首先要实现Cloneable接口,然后在重载clone方法,最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。
Java代码 复制代码
  1. A a1=new A();   
  2. A a2=new A();   
  3. a1.name="a1";   
  4. a2=a1;   
  5. a2.name="a2";   
  6. System.out.println("a1.name="+a1.name);   
  7. System.out.println("a2.name="+a2.name);  

此时输出的结果是:
Java代码 复制代码
  1. a1.name=a1   
  2. a2.name=a2  


当Class A成员变量类型是java的基本类型时(外加String类型),只要实现如上简单的clone(称影子clone)就可以。但是如果Class A成员变量是数组或复杂类型时,就必须实现深度clone。

Java代码 复制代码
  1. public class A implements Cloneable {   
  2.     public String name[];   
  3.        
  4.     public A(){   
  5.         name=new String[2];   
  6.     }   
  7.   
  8.     public Object clone() {   
  9.         A o = null;   
  10.         try {   
  11.             o = (A) super.clone();   
  12.         } catch (CloneNotSupportedException e) {   
  13.             e.printStackTrace();   
  14.         }   
  15.         return o;   
  16.     }   
  17. }  


测试代码

Java代码 复制代码
  1. A a1=new A();   
  2. A a2=new A();   
  3. a1.name[0]="a";   
  4. a1.name[1]="1";   
  5. a2=(A)a1.clone();   
  6. a2.name[0]="b";   
  7. a2.name[1]="1";   
  8. System.out.println("a1.name="+a1.name);   
  9. System.out.println("a1.name="+a1.name[0]+a1.name[1]);   
  10. System.out.println("a2.name="+a2.name);   
  11. System.out.println("a2.name="+a2.name[0]+a2.name[1]);  


输出结果:

Java代码 复制代码
  1. a1.name=[Ljava.lang.String;@757aef  
  2. a1.name=b1   
  3. a2.name=[Ljava.lang.String;@757aef  
  4. a2.name=b1  


看到了吧,a1.name,a2.name的hash值都是@757aef,也就是说影子clone对name数组只是clone他们的地址!解决该办法是进行深度clone。

Java代码 复制代码
  1. public Object clone() {   
  2.         A o = null;   
  3.         try {   
  4.             o = (A) super.clone();   
  5.             o.name=(String[])name.clone();//其实也很简单^_^   
  6.         } catch (CloneNotSupportedException e) {   
  7.             e.printStackTrace();   
  8.         }   
  9.         return o;   
  10.     }  


此时输出结果是:

Java代码 复制代码
  1. a1.name=[Ljava.lang.String;@757aef  
  2. a1.name=a1   
  3. a2.name=[Ljava.lang.String;@d9f9c3  
  4. a2.name=b1  


需要注意的是Class A存在更为复杂的成员变量时,如Vector等存储对象地址的容器时,就必须clone彻底。

Java代码 复制代码
  1. public class A implements Cloneable {   
  2.     public String name[];   
  3.     public Vector<B> claB;   
  4.        
  5.     public A(){   
  6.         name=new String[2];   
  7.         claB=new Vector<B>();   
  8.     }   
  9.   
  10.     public Object clone() {   
  11.         A o = null;   
  12.         try {   
  13.             o = (A) super.clone();   
  14.             o.name==(String[])name.clone();//深度clone   
  15.             o.claB=new Vector<B>();//将clone进行到底   
  16.             for(int i=0;i<claB.size();i++){   
  17.                 B temp=(B)claB.get(i).clone();//当然Class B也要实现相应clone方法   
  18.                 o.claB.add(temp);   
  19.             }   
  20.         } catch (CloneNotSupportedException e) {   
  21.             e.printStackTrace();   
  22.         }   
  23.                 return o;   
  24.     }   
  25. }   


Java克隆(Clone)的应用
 
 
简介:
 
Java克隆(Clone)是Java语言的特性之一,但在实际中应用比较少见。但有时候用克隆会更方便更有效率。
 
对于克隆(Clone),Java有一些限制:
1、被克隆的类必须自己实现Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。Cloneable 接口实际上是个标识接口,没有任何接口方法。
2、实现Cloneable接口的类应该使用公共方法重写 Object.clone(它是受保护的)。某个对象实现了此接口就克隆它是不可能的。即使 clone 方法是反射性调用的,也无法保证它将获得成功。
3、在Java.lang.Object类中克隆方法是这么定义的:
protected Object clone()
                throws CloneNotSupportedException
创建并返回此对象的一个副本。表明是一个受保护的方法,同一个包中可见。
按照惯例,返回的对象应该通过调用 super.clone 获得。
 
 
引题:
 
举个例子说吧,现在有一个对象比如叫foo,你需要在创建当前对象的一个副本作为存根你能怎么做?
 
假如你不用Clone,那么你可以先new一个对象foo1:Foo foo1=new Foo(),然后用foo给foo1对象set值,这样就得到foo的副本foo1;除此之外,别无选择。
 
这样说,也许有人会觉得说的过于绝对了,不过事实如此啊。
 
要产生一个副本,那副本要不要内存?----当然要了,那就对了!既然需要内存,(不克隆的情况下)你不new还有什么办法呢?请大家时刻铭记对象是Java运行时产生的,驻留在计算机内存中。
 
常见错误:
下面我澄清几个初学者容易犯迷糊的错误,同样的问题,产生foo对象的副本:
1、Foo foo1=new Foo();
   foo1=foo;
   然后就想当然的认为副本foo1生成了!
 
错误原因:foo1没错是申请了内存,但是执行foo1=foo后,foo1就不在指向刚申请的内存区域了,转而指向foo对象的内存区域,这时候,foo1、foo指向了同一内存区域。刚才new的操作制造一堆垃圾等着JVM回收。
 
2、Foo foo1=foo;
错误原因:还是两个变量都指向了同一块内存。
 
3、有些老鸟更厉害一些:在Foo中定义一个返回自身的方法:
    public Foo getInstance(){
        return this;
    }
 
    然后,Foo foo1=foo.getInstance();
 
错误原因:同上,主要还是没有重新开辟内存,this在对象里是什么?----就是对象自己的引用!那么getInstance()自然返回的就是对象自己,反正又是两个对象穿了一条裤子----***,哈哈。错得心服口服吧。为了节省篇幅,我在最后写个例子,留给那些对此有异议的人看。
 
引入克隆
 
看了这么多方法都不行,还很麻烦!干脆用克隆吧,简单明了。
 
废话不说了,看例子:
定义两个类CloneFooA、CloneFooB,然后写个测试类CloneDemo分别克隆这两个类的对象,然后打印测试结果到控制台。
 
 
/**
 * Created by IntelliJ IDEA.
 * User: leizhimin
 * Date: 2007-9-20
 * Time: 19:40:44
 * 简单类克隆实现
 * 要实现克隆,必须实现Cloneable接口,这是一个标识接口,没有接口方法
 * 实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。
 * 按照惯例,实现此接口的类应该使用公共方法重写 Object.clone(它是受保护的)。
 */
public class CloneFooA implements Cloneable {
    private String strA;
    private int intA;
 
    public CloneFooA(String strA, int intA) {
        this.strA = strA;
        this.intA = intA;
    }
 
    public String getStrA() {
        return strA;
    }
 
    public void setStrA(String strA) {
        this.strA = strA;
    }
 
    public int getIntA() {
        return intA;
    }
 
    public void setIntA(int intA) {
        this.intA = intA;
    }
 
    /**
     * @return 创建并返回此对象的一个副本。
     * @throws CloneNotSupportedException
     */
    public Object clone() throws CloneNotSupportedException {
        //直接调用父类的clone()方法,返回克隆副本
        return super.clone();
    }
}
 
 
/**
 * Created by IntelliJ IDEA.
 * User: leizhimin
 * Date: 2007-9-20
 * Time: 19:59:55
 * 深度克隆对象,当类存在聚合关系的时候,克隆就必须考虑聚合对象的克隆
 */
public class CloneFooB implements Cloneable {
    private CloneFooA fooA;
    private Double douB;
 
    public CloneFooB(Double douB) {
        this.douB = douB;
    }
 
    public CloneFooB(CloneFooA fooA, Double douB) {
        this.fooA = fooA;
        this.douB = douB;
    }
 
    public CloneFooA getFooA() {
        return fooA;
    }
 
    public void setFooA(CloneFooA fooA) {
        this.fooA = fooA;
    }
 
    public Double getDouB() {
        return douB;
    }
 
    public void setDouB(Double douB) {
        this.douB = douB;
    }
 
    /**
     * 克隆操作
     *
     * @return 自身对象的一个副本
     * @throws CloneNotSupportedException
     */
    public Object clone() throws CloneNotSupportedException {
        //先调用父类的克隆方法进行克隆操作
        CloneFooB cloneFooB = (CloneFooB) super.clone();
        //对于克隆后出的对象cloneFooB,如果其成员fooA为null,则不能调用clone(),否则出空指针异常
        if (this.fooA != null)
            cloneFooB.fooA = (CloneFooA) this.fooA.clone();
 
        return cloneFooB;
    }
}
 
 
/**
 * Created by IntelliJ IDEA.
 * User: leizhimin
 * Date: 2007-9-20
 * Time: 19:52:01
 * 测试类:分别克隆CloneFooA和CloneFooB类,并打印克隆前后的结果.
 */

public class CloneDemo {
    public static void main(String args[]) throws CloneNotSupportedException {
        //CloneFooA克隆前
        CloneFooA fooA1 = new CloneFooA("FooA", 11);
        System.out.println("CloneFooA的对象克隆前对象fooA1值为: " + fooA1.getStrA() + "," + fooA1.getIntA());
        //CloneFooA克隆后
        CloneFooA fooA2 = (CloneFooA) fooA1.clone();
        System.out.println("CloneFooA的对象克隆后对象fooA2值为: " + fooA2.getStrA() + "," + fooA2.getIntA());
        //比较fooA1和fooA2内存地址
        if (fooA1 == fooA2) System.out.println("比较fooA1和fooA2内存地址:相等!");
        else System.out.println("比较fooA1和fooA2内存地址:不相等!");
 
        System.out.println("-------------------------");
 
        //CloneFooB克隆前
        CloneFooB fooB1 = new CloneFooB(fooA1, new Double("33"));
        System.out.println("CloneFooB的对象克隆前对象fooB1值为: " + fooB1.getFooA().getStrA() + "," + fooB1.getFooA().getIntA() + " | " + fooB1.getDouB());
        //CloneFooB克隆后
        CloneFooB fooB2 = (CloneFooB) fooB1.clone();
        System.out.println("CloneFooB的对象克隆前对象fooB2值为: " + fooB2.getFooA().getStrA() + "," + fooB2.getFooA().getIntA() + " | " + fooB2.getDouB());
 
        if (fooA1 == fooA2) System.out.println("比较fooB1和fooB2内存地址:相等!");
        else System.out.println("比较fooB1和fooB2内存地址:不相等!");
    }
}
 
运行结果:
 
CloneFooA的对象克隆前对象fooA1值为: FooA,11
CloneFooA的对象克隆后对象fooA2值为: FooA,11
比较fooA1和fooA2内存地址:不相等!
-------------------------
CloneFooB的对象克隆前对象fooB1值为: FooA,11 | 33.0
CloneFooB的对象克隆前对象fooB2值为: FooA,11 | 33.0
比较fooB1和fooB2内存地址:不相等!
 
Process finished with exit code 0
 
 
反面教材:
 
最后,我给出我上面提出到最后要给出的反面例子。
 
随便写一个,在CloneFooA 的基础上做了少许改动,内容如下:
 
public class CloneFooA implements Cloneable {
    private String strA;
    private int intA;
 
    public CloneFooA(String strA, int intA) {
        this.strA = strA;
        this.intA = intA;
    }
 
    public String getStrA() {
        return strA;
    }
 
    public void setStrA(String strA) {
        this.strA = strA;
    }
 
    public int getIntA() {
        return intA;
    }
 
    public void setIntA(int intA) {
        this.intA = intA;
    }
 
    /**
     * @return 创建并返回此对象的一个副本。
     * @throws CloneNotSupportedException
     */
    public Object clone() throws CloneNotSupportedException {
        //直接调用父类的clone()方法,返回克隆副本
        return super.clone();
    }
 
    /**
     * @return 返回运行时的对象
     */
    public CloneFooA getInstance(){
        return this;
    }
 
    public static void main(String args[]){
        CloneFooA fooA=new CloneFooA("aa",11);
        System.out.println(fooA.getStrA()+"  "+fooA.getIntA());
 
        CloneFooA fooA1=fooA.getInstance();
        System.out.println(fooA1.getStrA()+"  "+fooA1.getIntA());
        if(fooA==fooA1) System.out.println("fooA和fooA1内存地址相等!");
 
        System.out.println("-------------------------");
 
        //改变后fooA或者fooA1中任何一个,看看另外一个是否会改变
        fooA1.setStrA("bb");
        System.out.println(fooA.getStrA()+"  "+fooA.getIntA());
        System.out.println(fooA1.getStrA()+"  "+fooA1.getIntA());
 
        if(fooA==fooA1) System.out.println("fooA和fooA1内存地址相等,改变fooA1后,fooA的值也跟着变化了");
    }
}
 
运行结果:
 
aa  11
aa  11
fooA和fooA1内存地址相等!
-------------------------
bb  11
bb  11
fooA和fooA1内存地址相等,改变fooA1后,fooA的值也跟着变化了
 
Process finished with exit code 0

 
 
 

本文出自 “熔 岩” 博客,转载请与作者联系!

 
 
 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多