Java 7之基础 - 浅克隆与深克隆

来源:互联网 发布:犀牛软件 mac 破解版 编辑:程序博客网 时间:2024/05/21 23:31
转载请注明出处:http://blog.csdn.net/mazhimazh/article/details/16828505
在使用克隆时,我们需要知道使用的目的:就是为了快速构造一个和已有对象相同的副本。如果克隆对象,一般需要先创建一个对象,然后将原对象中的数据导入到新创建的对象中去,而不用根据已有对象进行手动赋值操作。

1、克隆方法clone()


clone是定义一个Object类下基本方法之一,可以说,任何克隆的过程最终都将到达java.lang.Object 的clone()方法,而其在Object接口中定义如下:

protected native Object clone() throws CloneNotSupportedException;

有如下3点需要提示:

      (1)他是一个protected修饰的native方法,因此它的实现是取决于本地代码。native方法的效率一般来说都是远高于java中的非native方法。

      (2)Object中的clone方法是protected的,所以要使用clone就必须继承Object类(默认)。并且为了可以使其它类调用该方法,覆写克隆方法时必须将其作用域设置为public.

      (3)克隆方法返回的是一个Object对象,所以必须要经过强制类型转换。

       通常克隆对象都是通过调用super.clone()方法来获取克隆对象的,所以任何克隆的过程最终都将到达java.lang.Object 的clone()方法。但是在覆写clone()方法时,这个类需要继承Clonable接口,这个接口中没有定义方法,他类似于RandomAccess这些接口类,只做为一种标识存在。如果 clone 类没有实现 Cloneable 接口,并调用了 Object 的 clone() 方法(也就是调用了 super.Clone() 方法),那么Object 的 clone() 方法就会抛出 CloneNotSupportedException 异常。


通过从源码的注释中可以看出,克隆的一些特性:

(1)x.clone() != x 必须为真,也就是对于基础类型来说,其克隆后在堆中有两个独立且内容相同的内存区域。而对于引用类型来说,其引用也不相同。也就是说克隆对象和原始对象在java 堆(heap)中是两个独立的对

(2)x.clone().getClass() == x.getClass()  他们所属的类是同一个

(3) x.clone().equals(x)   所比较的对象内容相同

从上述的第二和第三点可以看出,克隆完全是拷贝一个独立的副本到内存中。但是由于克隆方法可以覆写,所以并不能保证克隆出来的对象能够达到(2)和(3)要求的标准,所以他们不是克隆方法所必须要求的。


2、浅克隆与深克隆

   

      克隆就是复制一个对象的复本.但一个对象中可能有基本数据类型,如:int,long,float等,也同时含有对象数据类型如(数组,集合等)。被克隆得到的对象基本类型的值修改了,原对象的值不会改变.这种适合shadow clone(浅克隆).

   但如果你要改变一个非基本类型的值时,原对象的值却改变了,.比如一个数组,内存中只copy他的地址,而这个地址指向的值并没有 copy,当clone时,两个地址指向了一个值,这样一旦这个值改变了,原来的值当然也变了,因为他们共用一个值.,这就必须得用深克隆(deep clone)

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]=500 ;        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=500
克隆后c2:  a=50 b[0]=500

可以看出,基本类型可以使用浅克隆,而对于引用类型,由于引用的是内容相同,所以改变c2实例对象中的属性就会影响到c1。所以引用类型需要使用深克隆。另外,在开发一个不可变类的时候,如果这个不可变类中成员有引用类型,则就需要通过深克隆来达到不可变的目的。


3、覆写clone方法深克隆


class bottle implements Cloneable {public wine wn;public bottle() {}public bottle(wine wn) {this.wn = wn;}// 覆写clone()方法protected Object clone() throws CloneNotSupportedException {bottle newBtl = (bottle) super.clone();return newBtl;}}class wine {int degree;     public int getDegree() {return degree;}public void setDegree(int degree) {this.degree = degree;}}public class test04 {public static void main(String[] args) throws CloneNotSupportedException {bottle bottle = new bottle(new wine());bottle bottle1 = (bottle) bottle.clone();System.out.println("bottle1.wine : " + bottle1.wn.getDegree() );bottle1.wn.setDegree(100);System.out.println("bottle1.wine : " + bottle1.wn.getDegree() );System.out.println("bottle.wine : " + bottle.wn.getDegree());}}
结果如下:

bottle1.wine : 0
bottle1.wine : 100
bottle.wine : 100

这就是浅克隆造成的问题,下面使用clone()来进行深拷贝:

class bottle implements Cloneable {public wine wn;public bottle(wine wn) {this.wn = wn;}// 覆写clone()方法protected Object clone() throws CloneNotSupportedException {bottle newBtl = (bottle) super.clone();newBtl.wn = (wine) wn.clone();return newBtl;}}class wine implements Cloneable {int degree;     public int getDegree() {return degree;}public void setDegree(int degree) {this.degree = degree;}// 覆写clone()方法protected Object clone() throws CloneNotSupportedException {return super.clone();}}
结果如下:

bottle1.wine : 0
bottle1.wine : 100
bottle.wine : 0

结果进行了正确的显示。如果要在wine中还有字符串类型该怎么办?例如:

class wine implements Cloneable {int degree;   String name="法国白兰地";public int getDegree() {return degree;}public void setDegree(int degree) {this.degree = degree;}public String getName() {return name;}public void setName(String name) {this.name = name;}// 覆写clone()方法protected Object clone() throws CloneNotSupportedException {return super.clone();}}
测试程序如下:

bottle bottle = new bottle(new wine());bottle bottle1 = (bottle) bottle.clone();System.out.println("bottle1.wine : " + bottle1.wn.getName() );bottle1.wn.setName("中国二锅头");System.out.println("bottle1.wine : " + bottle1.wn.getName() );System.out.println("bottle.wine : " + bottle.wn.getName());
结果如下:

bottle1.wine : 法国白兰地
bottle1.wine : 中国二锅头
bottle.wine : 法国白兰地  
  为什么会是法国白兰地?

单独对wine进行测试:

wine wn=new wine();wn.setName("法国葡萄酒");wine wn2=(wine)wn.clone();System.out.println(wn.getName());System.out.println(wn2.getName());
则结果如下:

法国葡萄酒
法国葡萄酒



4、使用序列化实现深克隆


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

可以看到,两个引用所指向的对象在堆中相互独立,互不干扰,这样就实现了深度克隆。

4、总结:

  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(),尽管这些不是必须的








原创粉丝点击