专栏首页Java知其所以然深克隆和浅克隆

深克隆和浅克隆

概述

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)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-01-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 前端入门21-JavaScript的ES6新特性声明正文-ES6新特性

    阮一峰的这本书,我个人觉得写得挺好的,不管是描述方面,还是例子,都讲得挺通俗易懂,每个新特性基本都还会跟 ES5 旧标准做比较,说明为什么会有这个新特性,这更于...

    请叫我大苏
  • SCRUM迭代开发 v0.2

    其中的“故事会”的说法是我从Daniel(滕振宇)老师的PO认证课上学来的。指的是在每个迭代中期,PO组织团队全员一起编写下一个迭代要做的用户故事及其验收条件,...

    吾真本
  • 前端入门20-JavaScript进阶之异步回调的执行时机声明正文-异步回调的执行时机

    作为一个前端小白,入门跟着这几个来源学习,感谢作者的分享,在其基础上,通过自己的理解,梳理出的知识点,或许有遗漏,或许有些理解是错误的,如有发现,欢迎指点下。

    请叫我大苏
  • Java异常知识点思考与总结

    Java 中的异常可以是方法执行过程中引发的,也可以是通过 throw 语句手动抛出的。一旦程序运行过程中发生了异常,JRE 就会试图寻找异常处理程序来处理异常...

    happyJared
  • java8 函数式编程一

    为什么要先从函数接口说起呢?因为我觉得这是 java8 函数式编程的入口呀!每个函数接口都带有 @FunctionalInterface 注释,有且仅有一个未实...

    JMCui
  • Java 控制台程序 JDBC连接数据库

    首先下载mysql-connector jar包 https://dev.mysql.com/downloads/file/?id=480090

    赵哥窟
  • Java|用 GitLab CI 进行持续集成:简介一些概念GitLab Runner.gitlab-ci.yml

    从 GitLab 8.0 开始,GitLab CI 就已经集成在 GitLab 中,我们只要在项目中添加一个 .gitlab-ci.yml 文件,然后添加一个 ...

    黄小怪
  • 前端入门14-JavaScript进阶之继承声明正文-继承

    作为一个前端小白,入门跟着这几个来源学习,感谢作者的分享,在其基础上,通过自己的理解,梳理出的知识点,或许有遗漏,或许有些理解是错误的,如有发现,欢迎指点下。

    请叫我大苏
  • 跨专业转CS拿下百度java后台开发的干货分享

    人们总以为时间是一个小偷,偷走了我们所爱的一切。但,时间是先给予再拿走,每天都是一份礼物,每小时,每一分,每一秒。——《爱丽丝梦游仙境2》

    牛客网
  • java小知识,大智慧

    java知识面很多,如果想系统的学习java知识,最好是看完一本相关书籍,其实这本书籍不用是什么名人写的,只要他有几年的开发经验基本上可以写出一个入门级的jav...

    哲洛不闹

扫码关注云+社区

领取腾讯云代金券