java浅克隆和深克隆

来源:互联网 发布:手机淘宝客链接转换器 编辑:程序博客网 时间:2024/05/21 09:53

Java支持我们对一个对象进行克隆,通常用在装饰模式和原型模式中。那么什么是深克隆,什么是浅克隆呢。

【浅克隆】,通常只是对克隆的实例进行复制,但里面的其他子对象,都是共用的。【深克隆】,克隆的时候会复制它的子对象的引用,里面所有的变量和子对象都是又额外拷贝了一份。  也就是说,一个默认的clone()方法实现机制,仍然是赋值。如果一个被复制的属性都是基本类型,那么只需要实现当前类的cloneable机制就可以了,此为浅拷贝。如果被复制对象的属性包含其他实体类对象引用,那么这些实体类对象都需要实现cloneable接口并覆盖clone()方法。

下面的两个例子可以很好的说明他们的区别:

  首先看一下类图
  这里写图片描述
  Husband类有一个对wife的引用,当进行浅克隆的时,wife变量都会指向同一个Wife;而进行深克隆时,会指向不同的Wife。下面进行一下验证:
  

java赋值

@Testpublic void testassign(){  Person p1=new Person();  p1.setAge(31);  p1.setName("Peter");  Person p2=p1;  System.out.println(p1==p2);//true}

如果创建一个对象的新的副本,也就是说他们的初始状态完全一样,但以后可以改变各自的状态,而互不影响,就需要用到java中对象的复制,如原生的clone()方法。

【浅克隆】

Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的,所以实体类使用克隆的前提是:

① 实现Cloneable接口,这是一个标记接口,自身没有方法。 ② 覆盖clone()方法,可见性提升为public。

public Object clone() {          Husband husband = null;          try{              husband = (Husband)super.clone();          }catch(CloneNotSupportedException e){              e.printStackTrace();          }finally{              return husband;          }     }

【深克隆】

public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();    ObjectOutputStream out = new ObjectOutputStream(byteOut);    out.writeObject(src);    ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());    ObjectInputStream in = new ObjectInputStream(byteIn);    List<T> dest = (List<T>) in.readObject();    return dest;}

【全部代码】

package entity;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import java.util.Date;class Wife implements Serializable {    private String name;    private Date birthday;    public Wife() {        name = "芙蓉姐姐";        birthday = new Date();    }    public Date getBirthday() {        return birthday;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}class Husband implements Cloneable, Serializable {    private Wife wife;    private Date birthday;    public Husband() {        wife = new Wife();        birthday = new Date();    }    public Wife getWife() {        return wife;    }    public Date getBirthday() {        return birthday;    }    /**     * 浅克隆一个对象     */    public Object clone() {        Husband husband = null;        try {            husband = (Husband) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        } finally {            return husband;        }    }    /**     * 利用串行化深克隆一个对象,把对象以及它的引用读到流里,在写入其他的对象     *      * @return     * @throws IOException     * @throws ClassNotFoundException     */    public Object deepClone() throws IOException, ClassNotFoundException {        // 将对象写到流里        ByteArrayOutputStream bos = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(bos);        oos.writeObject(this);        // 从流里读回来        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());        ObjectInputStream ois = new ObjectInputStream(bis);        return ois.readObject();    }}public class Test {    public static void main(String[] args) {        try {            Husband husband = new Husband();            System.out.println("husband birthday "                    + husband.getBirthday().getTime());            System.out.println("wife birthday "                    + husband.getWife().getBirthday().getTime());            System.out.println();            Husband husband1 = (Husband) husband.clone();            System.out.println("husband1 birthday "                    + husband1.getBirthday().getTime());            System.out.println("wife birthday "                    + husband1.getWife().getBirthday().getTime());            System.out.println();            System.out.println("是否是同一个husband " + (husband == husband1));            System.out.println("是否是同一个wife "                    + (husband.getWife() == husband1.getWife()));            System.out.println();            Husband husband2 = (Husband) husband.deepClone();            System.out.println("husband2 birthday "                    + husband2.getBirthday().getTime());            System.out.println("wife birthday "                    + husband2.getWife().getBirthday().getTime());            System.out.println();            System.out.println("是否是同一个husband " + (husband == husband2));            System.out.println("是否是同一个wife "                    + (husband.getWife() == husband2.getWife()));        } catch (Exception e) {            e.printStackTrace();        }    }}

【运行结果】

husband birthday 1508228006939wife birthday 1508228006939husband1 birthday 1508228006939wife birthday 1508228006939是否是同一个husband false是否是同一个wife truehusband2 birthday 1508228006939wife birthday 1508228006939是否是同一个husband false是否是同一个wife false

【总结】

clone方式深拷贝

① 如果有一个非原生成员,如自定义对象的成员,那么就需要:该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为public可见。同时,修改被复制类的clone()方法,增加成员的克隆逻辑。② 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法。与对象成员不同,继承关系中的clone不需要被复制类的clone()做多余的工作。一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。前面的实例还可以接受,如果有N个对象成员,有M层继承关系,就会很麻烦。

利用序列化实现深拷贝

clone机制不是强类型的限制,比如实现了Cloneable并没有强制继承链上的对象也实现;也没有强制要求覆盖clone()方法。因此编码过程中比较容易忽略其中一个环节,对于复杂的项目排查就是困难了。要寻找可靠的,简单的方法,序列化就是一种途径。被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。实现自己的deepClone方法,将this写入流,再读出来。俗称:冷冻-解冻。

原生的clone效率无疑是最高的,用脚趾头都能想到。 偶尔用一次,用哪个都问题都不大。 一般性能要求稍高的应用场景,cglib和orika完全可以接受。 另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。

原创粉丝点击