深克隆和浅克隆

丶灬走出姿态 提交于 2020-11-22 15:30:19
点击上方 “蓝字” 可以关注我哦!

概述

Java 集合中提供的拷贝构造函数只支持浅拷贝而不是深拷贝,这是因为集合中的拷贝构造函数是通过引用的复制来达到浅拷贝的。这意味着存储在原有集合和克隆集合中的对象会保持一致(指向同一内存地址)。当然如果集合中的对象是不可变对象,那这是可以的。这也是为什么 String 设计为不可变类之一的原因。String 对象在字符串常量池中更新一个并不会影响到其他对象,便于缓存字符串。

public final class String
     implements java.io.Serializable, Comparable<String>, CharSequence {
     /** The value is used for character storage. */
     private final char value[];
     
     public String(String original) {
         this.value = original.value;
         this.hash = original.hash;
     }
     // ......
 }

String 类底层是利用一个 char 数组实现的,并且没有对外提供修改 char 数组的方法。因为它是一个不可变的类,所以可以在拷贝构造函数中让两个字符串对象指向同一个地址也并不会相互影响。

浅拷贝

定义

浅拷贝其实是把一个对象的值复制一份到克隆的对象中。不需要去执行构造函数,所以效率会快很多,这是浅拷贝的一个优点。但是对于可变类来说,克隆以后对象的值并没有和原对象分离开来,而是相互影响,所以这是浅拷贝的一个缺点。


代码示例

public class Sheep implements Cloneable,Serializable {
     private String sname;
     private Date birthday;
 
     public Sheep(String sname, Date birthday) {
         super();
         this.sname = sname;
         this.birthday = birthday;
     }
 
     public Sheep() {
     }
     
     @Override
     protected Object clone() throws CloneNotSupportedException {
         // 直接调用 object 对象的 clone()方法
         Object obj = super.clone();  
         return obj;
     }
     
     // ...... 省略 get、set
 }

测试类

public class Client {
     public static void main(String[] args) throws Exception {
         Date date = new Date(12312221331L);
         Sheep originalObject = new Sheep("少利",date);
         System.out.println(originalObject);
         
         // originalObject 和destObject 中的 birthday 都指向的是 date 这个对象,如果 date 改变,它们两个都会受到影响。  
         Sheep destObject = (Sheep) originalObject.clone();
         System.out.println(destObject);
     }
 }

深拷贝

定义

深拷贝其实是把一个对象的值复制一份到克隆的对象中并为每个可变类属性创建内存空间。这样的话,克隆后的对象的值和原来的对象的值互不影响。因为他们指向的是堆内存中不同的内存空间。

实现方案

  1. 重写 Object 的 clone 方法,并将每个可变类属性也克隆一次。

  2. 利用序列化和反序列化。

代码示例

public class Sheep2 implements Cloneable {  
     private String sname;
     private Date birthday;
 
     @Override
     protected Object clone() throws CloneNotSupportedException {
         Object obj = super.clone();  
         
         // 添加如下代码实现深复制(deep Clone),只需要将可变类属性克隆一下即可,对于不可变类属性不需要理会。
         Sheep2 s = (Sheep2) obj;
         // 把可变类属性也进行克隆
         s.birthday = (Date) this.birthday.clone();  
         return obj;
     }
 
     public Sheep2(String sname, Date birthday) {
         super();
         this.sname = sname;
         this.birthday = birthday;
     }
 
     public Sheep2() {
     }
 }

测试类(实现方案一)

public class Client2 {
     public static void main(String[] args) throws CloneNotSupportedException {
         // clone
         Date date = new Date(12312321331L);
         Sheep2 originalObject = new Sheep2("少利",date);
         // 实现深复制。s2 对象的 birthday 是一个新对象
         Sheep2 destObject = (Sheep2) originalObject.clone();  
         
         System.out.println(originalObject);
         System.out.println(destObject);
     }
 }

测试类(实现方案二)

public class Client2 {
     public static void main(String[] args) throws Exception {
         // 序列化和反序列化,注意要对序列化的类实现 Serializable 接口。
         Date date = new Date(12312321331L);
         Sheep originalObject = new Sheep("少利",date);
         
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         ObjectOutputStream    oos = new ObjectOutputStream(bos);
         oos.writeObject(originalObject);
         byte[] bytes = bos.toByteArray();
         
         ByteArrayInputStream  bis = new ByteArrayInputStream(bytes);
         ObjectInputStream    ois = new ObjectInputStream(bis);
         // 克隆的对象
         Sheep destObject = (Sheep) ois.readObject();  
     }
 }

clone 的替代方案

使用 clone() 方法来拷贝一个对象它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。

ArrayList 深拷贝

ArrayList#addAll 方法只是浅拷贝,指向的是同一堆内存。

如何进行深拷贝呢

  1. 重写集合中所存储的对象的 clone 方法

  2. 通过集合迭代器遍历原集合,将原集合中的每个元素调用 clone 并添加到新集合中

代码示例

public class Employee implements Cloneable{
     private String name;
     private String designation;
     
     public Employee(String name, String designation) {
         this.name = name; this.designation = designation;
     }
     @Override
     protected Employee clone() {
         Employee clone = null;
         try{
             clone = (Employee) super.clone();
 
         }catch(CloneNotSupportedException e){
             throw new RuntimeException(e);  // won't happen
         }
         return clone;
     }
     // ...... 省略 get、set
 }

测试类

public class CollectionCloningTest {
     
     public static void main(String args[]) {
         Collection org = new HashSet();
         org.add(new Employee("Joe", "Manager"));
         org.add(new Employee("Tim", "Developer"));
         org.add(new Employee("Frank", "Developer"));
 
         Collection<Employee> copy = new HashSet<Employee>(org.size());
         Iterator<Employee> iterator = org.iterator();
         while(iterator.hasNext()){
             copy.add(iterator.next().clone());
         }
     }
 }

总结

clone 的方式来创建对象比 new 关键字创建对象效率好,尤其是在需要大量创建对象时尤为明显。浅克隆拷贝的值(对象的话就是引用值),使用的同一块内存空间。深拷贝拷贝值的同时还创建了内存空间,使用的是不同的内存空间。不可变类对象的拷贝使用浅克隆就行。

参考链接:https://blog.csdn.net/cool_sti/article/details/21658521

参考书籍:《Effective Java 中文版》


         


现在的喜欢,其实不是真正的喜欢,只是因为不了解。真正的喜欢,是建立在非常了解的基础之上的。 



本文分享自微信公众号 - Java知其所以然(gh_37a1335e2608)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!