Java中的浅克隆和深克隆

来源:互联网 发布:平安科技待遇 知乎 编辑:程序博客网 时间:2024/05/17 23:27

一、为什么要实现克隆

为什么需要克隆对象?直接new一个对象不行吗?
首先我们需要明白克隆的对象可能不是新建出来的对象,它包含一些已经修改过的属性(比如修改成员变量的值),而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。
那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是过程十分繁琐。
而且我们常见的

Object a=new Object();Object b;b=a;

这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。而通过clone方法赋值的对象跟原来的对象时同时独立存在的。
在Object提供的方法中,有一个访问限定符为protected的方法clone(),其源码如下:

/*Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.The general intent is that, for any object x, the expression:1) x.clone() != x will be true2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.3) x.clone().equals(x) will be true, this is not an absolute requirement.*/protected native Object clone() throws CloneNotSupportedException;

可以看到这是一个native方法,native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言(例如C)来实现。
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。如果我们要想对一个对象进行复制,就需要对clone方法重写。

二、浅克隆

在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。
浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复值。

进行浅克隆一般有如下步骤:
1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法,类似Serializable)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)。
示例如下:

public class Student implements Cloneable{      private int number;      public int getNumber() {          return number;      }      public void setNumber(int number) {          this.number = number;      }      @Override      public Object clone() {          Student stu = null;          try{              stu = (Student)super.clone();          }catch(CloneNotSupportedException e) {              e.printStackTrace();          }          return stu;      }  }  

三、深克隆

当一个对象中包含其他类对象的引用时,我们使用上面的方法实现的克隆并不是真正的克隆。这时我们需要覆盖重写Object中的clone方法。示例如下:

class Address implements Cloneable {      private String add;      public String getAdd() {          return add;      }      public void setAdd(String add) {          this.add = add;      }      @Override      public Object clone() {          Address addr = null;          try{              addr = (Address)super.clone();          }catch(CloneNotSupportedException e) {              e.printStackTrace();          }          return addr;      }  }  public class Student implements Cloneable{      private int number;      private Address addr;      public Address getAddr() {          return addr;      }      public void setAddr(Address addr) {          this.addr = addr;      }      public int getNumber() {          return number;      }      public void setNumber(int number) {          this.number = number;      }      @Override      public Object clone() {          Student stu = null;          try{              stu = (Student)super.clone();   //浅复制          }catch(CloneNotSupportedException e) {              e.printStackTrace();          }          stu.addr = (Address)addr.clone();   //深度复制          return stu;      }  }  

可以看到,实现深克隆时我们需要把该类所包含的所有其他类都实现Cloneable接口,并且在该类的Clone方法中调用其他类的Clone方法。这个过程还可能涉及很多层(例如A包含有B的对象,而B又包含C的对象),这样实现起来十分繁琐。

在Java语言中,如果需要实现深克隆,还可以通过序列化(Serialization)的方式来实现。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

四、序列化实现深克隆

基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

package com.kang;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;class Address implements Serializable {//必须实现Serializable接口,否则序列化时报错    private static final long serialVersionUID = 1L;    private String add;    public String getAdd() {        return add;    }    public void setAdd(String add) {        this.add = add;    }}public class Student implements Serializable {    private static final long serialVersionUID = 1L;    private int number;    private Address addr;//成员变量中涉及的类都必须实现序列化接口    public Address getAddr() {        return addr;    }    public void setAddr(Address addr) {        this.addr = addr;    }    public int getNumber() {        return number;    }    public void setNumber(int number) {        this.number = number;    }    public Student myclone() {        Student student = null;        try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝            ByteArrayOutputStream baos = new ByteArrayOutputStream();//可以捕获内存缓冲区的数据,转换成字节数组。            ObjectOutputStream oos = new ObjectOutputStream(baos);            oos.writeObject(this);//序列化            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());            ObjectInputStream ois = new ObjectInputStream(bais);            student = (Student) ois.readObject();//反序列化得到深度克隆结果        } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }        return student;    }}
原创粉丝点击