Java进阶系列之对象克隆

作者: Neulana | 来源:发表于2016-07-26 01:58 被阅读931次

大家都知道在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码,你可以把你的JDK目录下的src.zip复制到其他地方然后解压,里面就是所有的源码。发现里面有一个访问限定符为protected的方法clone():

protected native Object clone() throws CloneNotSupportedException;

仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。

为什么要克隆?

大家先思考一个问题,为什么需要克隆对象?直接new一个对象不行吗?答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。提个醒,我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。而通过clone方法赋值的对象跟原来的对象时同时独立存在的。扯了这么多废话,终于进入正题了ORZ。

如何实现克隆

简单到你不敢相信。直接在你的类的后面声明implements Cloneable。关于这个接口,它的源码如下:

public interface Cloneable {
}

可以看到它是一个空的接口,它的作用就是做标记。如果没有实现Cloneable接口就直接使用clone方法,程序会抛出CloneNotSupportedException异常。
然后是重写clone方法,并修改成public访问级别。举个经典的栗子:

class Outer implements Cloneable {
    public int name;
    public Inner inner;
      
    @Override
    public Object clone() throws CloneNotSupportedException {
      return super.clone();
    }
public static void main(String[] args){
    Outer o_one = new Outer();
    o_one.inner = new Inner("zhangsan");
    try {
    Object obj = o_one.clone();
    Outer o_two = (Outer)obj;
    System.out.println(o_one==o_two);    //打印false
    System.out.println(o_one.inner.name.equals(o_two.inner.name)); //打印true
    } catch (CloneNotSupportedException e) {
     e.printStackTrace();
    }
}
}

上面的代码实现的其实是浅克隆,浅克隆对于引用类型仅拷贝引用,没有真正地让两个对象独立开来互相之间没有任何关系。由于是浅克隆,使得imp2修改了child的某个属性后会是的imp1中child的属性也跟着改变。或者比较两个对象的地址:imp1.child==imp2.child,返回的结果是true。但是,如果一个对象只包含原始数据域或者不可变对象域(比如String类型),推荐使用浅克隆。

深克隆

类中的所有引用类型做一些修改,让它也实现Cloneable接口。然后修改本类中的clone方法:

public class Inner implements  Cloneable{

  public String name;

  public Child(String name) {
      this.name = name;
  }

  @Override
  public String toString() {
      return "Inner的name值为:" + name;
  }

  @Override
  protected Object clone() throws CloneNotSupportedException {
      return super.clone();
  }
}

修改本类的clone方法:

static class Outer implements Cloneable {
  public int count;
  public Inner inner;
      
      
  @Override
  public Object clone() throws CloneNotSupportedException {
      Outer obj = (Outer)super.clone();
      obj.inner = (Inner) inner.clone();
      return obj;
  }
}

画重点了:

  • 需要重写clone方法,不仅仅只调用父类的方法,还需调用属性的clone方法;
  • 对象之间100%数据分离
  • 如果是对象存在引用类型的属性,建议使用深克隆
  • 深克隆比浅克隆要更加耗时,效率更低

解决多层克隆问题

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

public class Outer implements Serializable{
  private static final long serialVersionUID = 369285298572941L;  //最好是显式声明ID
  public Inner inner;
  
  public Outer myclone() {
      Outer outer = null;
      try {
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          ObjectOutputStream oos = new ObjectOutputStream(baos);
          oos.writeObject(this);
  
          ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
          ObjectInputStream ois = new ObjectInputStream(bais);
          outer = (Outer) ois.readObject();
      } catch (IOException e) {
          e.printStackTrace();
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
      return outer;
  }
}

Inner也必须实现Serializable,否则无法序列化:

public class Inner implements Serializable{
  private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
  public String name = "";

  public Inner(String name) {
      this.name = name;
  }

  @Override
  public String toString() {
      return "Inner的name值为:" + name;
  }
}

这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。

Tips

  • 在克隆方法中,如果我们需要对可变对象的final域也进行拷贝,由于final的限制,所以实际上是无法编译通过的。因此为了实现克隆,我们需要考虑舍去该可变对象域的final关键字。
  • 如果你决定用线程安全的类实现Cloneable接口,需要保证它的clone方法做好同步工作。默认的Object.clone方法是没有做同步的。

相关文章

  • Java进阶系列之对象克隆

    大家都知道在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码,你可以把你的JDK...

  • 使用对象序列化和反序列化实现深度克隆

    java对象进行克隆,分为浅克隆和深克隆 浅克隆需要实现java.lang.Cloneable接口,并重写java...

  • java克隆

    java克隆 java克隆分为浅克隆和深克隆,概念如下: 浅拷贝(浅克隆)克隆出来的对象的所有变量都含有与原来的对...

  • 理解Java浅克隆和深克隆

    克隆概念 Java一切皆对象,克隆就是对对象的克隆;克隆可能听起来有点高级,也可以为对象复制或者对象拷贝。平时开发...

  • Java深复制浅复制解析.md

    Java 克隆概念 Java克隆分为深克隆和浅克隆两种类型。 浅复制(浅克隆)被复制对象的所有变量都含有与原来的对...

  • Java | 对象克隆

    浅拷贝 默认的克隆操作,也就是Object的 clone 方法是浅拷贝,浅拷贝只会拷贝对象属性的值,而不会拷贝对象...

  • Java对象克隆

    在Java中数据类型分为值(基本数据类型)类型和引用类型。深克隆和浅克隆的重要区别在于是否支持引用类型(数组、类、...

  • Java对象克隆

    引自:Java对象克隆(Clone)及Cloneable接口、Serializable接口的深入探讨https:/...

  • Java 对象克隆

    所谓的对象克隆描述的概念就是进行对象的赋值,当一个对象创建完成之后实际上都会自动的开辟内存空间,在每一块堆内存空间...

  • java 克隆对象

    P:java分基本数据类型和引用数据类型。 (1)假克隆: 对于 = 等于号的克隆形式, 基本数据类型是值拷贝.相...

网友评论

    本文标题: Java进阶系列之对象克隆

    本文链接:https://www.haomeiwen.com/subject/mernjttx.html