java中的对象的深克隆和浅克隆

本文参考链接

一、克隆对象从简单的变量克隆入手,就是以下代码
int apples = 5;  
int pears = apples; 
不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。
二、克隆对象从简单的方法入手,就是以下代码
package com.xuecheng.test.freemarker;

import org.junit.Test;

public class Test001 {
    @Test
    public void ss(){
        Student s1 = new Student();
        s1.setAge(12);
        Student s2 = s1;
        System.out.println(s1);
        System.out.println(s2);
    }
}

打印结果为:
Student{name='null', age=12}
Student{name='null', age=12}
但是真有这么简单吗?我们给s2对象中的一个属性改变后
 s2.setAge(18);
打印结果为:
Student{name='null', age=18}
Student{name='null', age=18}
三、总结
为什么把s2的属性改变后,s1的属性也改变了?原因出在 s2 = s1上,这一部是将s1的引用赋值给s2。也就是说s1和s2指向堆中的同一对象。
四、为什么要进行对象克隆吗?为什么不能直接new 一个吗?

因为被克隆对象有些属性是被改过的,而new一个对象的话,他的属性都是初始值。而如果你通过new出来的对象,一个一个进行赋值的话。一、可能比较麻烦。二、因为通过Object提供的方法clone,这个clone方法是native修饰的所以执行效率会比较快。
而上面一个对象给另外一个对象赋值时,这种是引用,即引用的是一个地址值,还是指向同一个对象。Object提供的这个clone方法是独立的两个对象。

五、如何进行对象的克隆

5.1、使被克隆对象实现cloneable(该接口只是标识接口,没有任何方法)
5.2、并在被克隆对象中重写Object中的clone方法。并且把protected改成public

六、以上是浅克隆,下面谈谈浅克隆和深克隆的区别
6.1、浅克隆

浅克隆就是,如果对象的属性是值类型就克隆该值类型;如果是引用类型,就克隆其引用地址。所以说浅克隆的属性是引用类型的话,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
所以说浅克隆只是克隆了属性是值类型的对象,而对属性是引用类型的对象并没有克隆。
在这里插入图片描述

6.2、深克隆

深克隆就是不仅将原型对象的值类型克隆,还将原型对象的引用类型也进行了克隆。是真正的克隆,引用类型的克隆后,完全是独立的两个地址,而不是仅仅克隆引用。
在这里插入图片描述
但是如果克隆对象里,有引用类型,但是引用类型对象里,又有引用类型。这样嵌套了好几层的引用类型。使用clone方法就很麻烦,这时,我们就可以使用序列化来简单实现。因为对象序列化写在流中,就是对象的一个拷贝,在内存中原有的对象还在。在流中,不仅可以拷贝对象,还可以拷贝其中的引用对象。
因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。
需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

-------------------------扩展----------------------------

Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。

七、解决多层克隆问题

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

public class Outer implements Serializable{
  private static final long serialVersionUID = 369285298572941L;  //最好是显式声明ID
  public Inner inner;
 //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化]
  public Outer myclone() {
      Outer outer = 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);
          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;
  }
}
这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。

总结

实现对象克隆有两种方式:

1). 实现Cloneable接口并重写Object类中的clone()方法;

2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值