Java 克隆--浅拷贝和深拷贝

阅读前沿概括:
1 通常变量直接赋值对应引用类型来说就是把变量指向了同一个地址,对于基本类型/string来说就是真的赋值。
2 而浅拷贝则覆盖了Object.clone()和implemnents Cloneable来实现,可以把当前被拷贝对象实例复制一份出来独立,但是对于引用类型(除了String)成员变量来说其拷贝的依旧是地址,而不是在内存拷贝多处一个副本然后指向过去。
3 对于2中的问题,深拷贝就能解决了,只不过他要在每个被拷贝的引用类型成员变量中覆盖clone方法,并且将其引用类型的成员变量的clone()副本赋值到被拷贝的类对应成员变量上。这样如果被拷贝的引用类型成员变量里面还有引用类型成员变量,那同样也要这样做,这样复杂度很麻烦,而且类似StringBuilder.java这些系统自定义的就需要重新实现,为其子类加上clone()和implements Cloneable。
4 对于3中的每个引用类型的都要加上clone()和implements Cloneable的问题,可用序列化解决,但是也是要每个都要implements Serializable,这样还不如用3的方法,3的方法还能定制化,哪个成员变量想被拷贝就帮她加上clone()和implements Cloneable。
 

通常变量复制如下:
//基本的引用复制
Sub sub = new Sub();
Sub sub2 = sub;
System.out.println(sub.subName + "," + sub2.subName);
sub2.subName = "更新的名字";
System.out.println(sub.subName + "," + sub2.subName);
解析:由于Sub是引用类型,所以上面复制的是变量的引用(如果是基本类型则复制的是值),所以将sub2=sub后,两个变量会指向同一个内存地址如下:
 

 

复制变量是这样复制的,但是!!Java是怎么解决引用类型复制到的是引用的问题?
解决办法:浅拷贝、深拷贝。
 
什么是浅拷贝?
通过覆盖Object.clone(),并且implements Cloneable来实现浅拷贝。其中implements Cloneable仅作为标记当前类可被拷贝,不继承的话抛CloneNotSupportedException异常。
浅拷贝,会复制多一份对象实例到内存,成员变量基本数据类型、String类型也会复制一份值出来,而引用类型则只会拷贝地址。
具体结合如下代码讲解:
//实验拷贝的类
class ShallowClone implements Cloneable {
    public int age = 1; //基本数据类型
    public String name = "初始名字"; //String引用数据类型,但是String特殊
    public Sub sub = new Sub(); //引用类型
    @Override
    public ShallowClone clone() throws CloneNotSupportedException {
        return (ShallowClone) super.clone();
    }
}
//实验拷贝的类中的成员变量(自定义的引用类型)
class Sub {  
    public String subName = "Sub初始名字";
    public SubSub subSub = new SubSub();
}
//实验拷贝的类中的成员变量中的成员变量(用于实验深层拷贝)
class SubSub {
    public String subSubName = "SubSub初始名字";
    //还有很多层SubSubSub...的拷贝下去
    }
}
//具体使用方法
@Test
public void test01() throws CloneNotSupportedException {
    ShallowClone shallowClone = new ShallowClone();
    ShallowClone shallowCloneCopy = shallowClone.clone();
    //测试成员变量的
    System.out.println("拷贝测试:\n" //1 更新前输出各个不同类型的成员变量比较结果
        "基本数据类型 shallowClone.age == shallowCloneCopy.age:" + (shallowClone.age == shallowCloneCopy.age) + "\n" +
        "String类型 shallowClone.name == shallowCloneCopy.name:" + (shallowClone.name == shallowCloneCopy.name) + "\n" +
        "引用类型 shallowClone.sub == shallowCloneCopy.sub:" + (shallowClone.sub == shallowCloneCopy.sub));
    System.out.println(String.format("更新前:\nshallowClone.age:%d,shallowCloneCopy.age:%d", shallowClone.age, shallowCloneCopy.age));
    System.out.println(String.format("shallowClone.name:%s,shallowCloneCopy.name:%s", shallowClone.name, shallowCloneCopy.name));
    System.out.println(String.format("shallowClone.sub:%s,shallowCloneCopy.sub:%s", shallowClone.sub.hashCode(), shallowCloneCopy.sub.hashCode()));
    shallowCloneCopy.age = 2 // 2.1 更新成员变量(基本数据类型)
    shallowCloneCopy.name = "更新后";  // 2.2 更新成员变量(String类型)
    shallowCloneCopy.sub.subName = "Sub更新名字";// 2.3 更新成员变量(引用数据类型)
    System.out.println(String.format("更新后:\nshallowClone.age:%d,shallowCloneCopy.age:%d", shallowClone.age, shallowCloneCopy.age));
    System.out.println(String.format("shallowClone.name:%s,shallowCloneCopy.name:%s", shallowClone.name, shallowCloneCopy.name));
    System.out.println(String.format("shallowClone.sub.subName:%s,shallowCloneCopy.sub.subName:%s", shallowClone.sub.subName, shallowCloneCopy.sub.subName));
    System.out.println("拷贝测试:\n" +    
        //3 更新后发现基本数据类型、String类型都没影响被拷贝的实例,说明他们在内存中被拷贝出了一个副本,而引用类型sub没有被拷贝出一个复制,只是单纯的拷贝了引用,导致一个改变,被拷贝的实例也发生改变
        "基本数据类型 shallowClone.age == shallowCloneCopy.age:" + (shallowClone.age == shallowCloneCopy.age) + "\n" +
        "String类型 shallowClone.name == shallowCloneCopy.name:" + (shallowClone.name == shallowCloneCopy.name) + "\n" +
        "引用类型 shallowClone.sub == shallowCloneCopy.sub:" + (shallowClone.sub == shallowCloneCopy.sub));
}
会输出如下结果:
拷贝测试:
基本数据类型 shallowClone.age == shallowCloneCopy.age:true
String类型  shallowClone.name == shallowCloneCopy.name:true
引用类型    shallowClone.sub == shallowCloneCopy.sub:true
更新前:
shallowClone.age:1,shallowCloneCopy.age:1
shallowClone.name:初始名字,shallowCloneCopy.name:初始名字
shallowClone.sub:31629417,shallowCloneCopy.sub:31629417
更新后:
shallowClone.age:1,shallowCloneCopy.age:2
shallowClone.name:初始名字,shallowCloneCopy.name:更新后
shallowClone.sub.subName:Sub更新名字,shallowCloneCopy.sub.subName:Sub更新名字
拷贝测试:
基本数据类型 shallowClone.age == shallowCloneCopy.age:false
String类型  shallowClone.name == shallowCloneCopy.name:false
引用类型    shallowClone.sub == shallowCloneCopy.sub:true
 

什么是深拷贝?
有了上面的浅拷贝,我们发现浅拷贝并不能拷贝到引用类型的成员变量的值(除了String),只会拷贝到其引用地址。这样可通过深拷贝来解决引用类型成员变量只拷贝到了内中引用地址的情况
通过修改被拷贝类clone()方法,将其成员变量的拷贝也副本赋值进去就能实现深拷贝。
具体代码修改如下:
//实验拷贝的类
class DeepClone implements Cloneable {
    public int age = 1; //基本数据类型
    public String name = "初始名字"; //String引用数据类型,但是String特殊
    public Sub sub = new Sub(); //引用类型
    @Override
    public DeepClone clone() throws CloneNotSupportedException {
        DeepClone deepClone = (DeepClone) super.clone();
        deepClone.sub = sub.clone();   //!!!重点!!!
        return deepClone;
    }
}
//修改实验拷贝的类中的成员变量(自定义的引用类型),为了实现深拷贝帮他加上clone(),并在里面也将引用类型的成本变量clone加进去
class Sub {  
    public String subName = "Sub初始名字";
    public SubSub subSub = new SubSub();
    @Override
    public Sub clone() throws CloneNotSupportedException {
        Sub sub = (Sub)super.clone();
        sub.subSub = subSub.clone();
        return sub;
    }
}
//修改实验拷贝的类中的成员变量中的成员变量(用于实验深层拷贝),,为了实现深拷贝帮他加上clone()
class SubSub {
    public String subSubName = "SubSub初始名字";
    //还有很多层SubSubSub...的拷贝下去
    //public SubSub clone() {...................}
    }
}
 

但是!!就算有了深拷贝也不行,因为像上面这样实现深拷贝的话,那每个深拷贝的子类都需要特殊定义clone()方法以及implements Cloneable,这样太麻烦了。当然如果没有需求要深层拷贝所有的成员变量里面的成员变量,那这样的就不需要每个引用类型的成员变量都定义clone。
但是想实现所有深层的成员变量都拷贝出来如何实现(在不覆盖clone()情况下)?
可通过序列化来实现:以下代码例子参照https://www.cnblogs.com/Qian123/p/5710533.html#_label2
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;
  }
}
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;
  }
}
 
参考文章:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值