《阿里巴巴Java开发手册》关于 Object 的 clone 问题的描述 :

【推荐】慎用 Object 的 clone 方法来拷贝对象。

说明:对象 clone 方法默认是浅拷贝,若想实现深拷贝需覆写 clone 方法实现域对象的深度遍历式拷贝。

1. 什么是拷贝?

对象的拷贝,就是根据原来的对象 “复制” 一份属性、状态一致的新的对象。

2. 为什么需要拷贝

  • 为了复制一个已经存在的对象,通过修改部分属性的值可以快速得到一个新的对象
    • 降低创建对象的复杂度,不需要从头开始去创建对象
    • 降低了创建对象带来的性能消耗
  • 多线程环境下,可以每个线程操作不同的对象拷贝,互相不影响

    3. 什么是浅拷贝?浅拷贝和深拷贝的区别是什么?

    Objectclone函数默认是浅拷贝。
    1. protected native Object clone() throws CloneNotSupportedException;
    浅拷贝:对于基本数据类型复制的是值,对于引用类型复制的是引用,而不是对象本身。
    深拷贝:无论是基本数据类型还是引用类型,都是对对象值的拷贝。

    Java 中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。

浅拷贝和深拷贝的主要区别在于对于引用类型是否共享。
image.png

4. Java 拷贝需要实现接口Cloneable

  • 如果调用clone函数的类没有实现Cloneable接口将会抛出CloneNotSupportedException。因此要实现Cloneable接口。
  • 重写clone函数是为了供外部使用,因此定义为public
  • 返回值类型定义为客户端直接需要的对象类型(本类)。

    5. 浅拷贝特点

  • 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。

  • 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。

    1. ![](https://cdn.nlark.com/yuque/0/2021/webp/12535721/1612515612269-452ba1d1-4578-432c-a2a2-b2885039031c.webp#crop=0.0612&crop=0.1054&crop=0.9746&crop=0.9217&height=303&id=S0JSU&originHeight=332&originWidth=670&originalType=binary&ratio=1&rotation=0&showTitle=false&size=0&status=done&style=none&title=&width=612)

    6. 浅拷贝的实现

    实现对象拷贝的类,需要实现 Cloneable 接口,并覆写 clone() 方法。

    1. public class Subject {
    2. private String name;
    3. public Subject(String name) {
    4. this.name = name;
    5. }
    6. public String getName() {
    7. return name;
    8. }
    9. public void setName(String name) {
    10. this.name = name;
    11. }
    12. @Override
    13. public String toString() {
    14. return "[Subject: " + this.hashCode() + ",name:" + name + "]";
    15. }
    16. }
    1. public class Student implements Cloneable {
    2. //引用类型
    3. private Subject subject;
    4. //基本数据类型
    5. private String name;
    6. private int age;
    7. //getter/setter
    8. /**
    9. * 重写clone()方法
    10. */
    11. @Override
    12. public Object clone() {
    13. try {
    14. // 直接调用父类的clone()方法 浅拷贝
    15. return super.clone();
    16. } catch (CloneNotSupportedException e) {
    17. return null;
    18. }
    19. }
    20. @Override
    21. public String toString() {
    22. return "[Student hashcode: " + this.hashCode() + ",subject:" + subject +
    23. ",name:" + name + ",age:" + age + "]";
    24. }
    25. }
    1. public class ShallowCopy {
    2. public static void main(String[] args) {
    3. Subject subject = new Subject("yuwen");
    4. Student studentA = new Student();
    5. studentA.setSubject(subject);
    6. studentA.setName("Lynn");
    7. studentA.setAge(20);
    8. //拷贝一个对象
    9. Student studentB = (Student) studentA.clone();
    10. studentB.setName("Lily");
    11. studentB.setAge(18);
    12. Subject subjectB = studentB.getSubject();
    13. subjectB.setName("lishi");
    14. System.out.println("studentA:" + studentA.toString());
    15. System.out.println("studentB:" + studentB.toString());
    16. }
    17. }
    1. studentA:[Student hashcode: 1554874502,subject:[Subject: 1846274136,name:lishi],name:Lynn,age:20]
    2. studentB:[Student hashcode: 1639705018,subject:[Subject: 1846274136,name:lishi],name:Lily,age:18]

    由输出的结果可见,通过 studentA.clone() 拷贝对象后得到的 studentB,和 studentA 是两个不同的对象。studentAstudentB 的基础数据类型的修改互不影响,而引用类型 subject 修改后是会有影响的。

    7. 深拷贝介绍

    通过上面的例子可以看到,浅拷贝会带来数据安全方面的隐患,例如我们只是想修改了 studentBsubject,但是 studentAsubject 也被修改了,因为它们都是指向的同一个地址。所以,此种情况下,我们需要用到深拷贝。

深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。

8. 深拷贝特点

  • 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
  • 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
  • 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
  • 深拷贝相比于浅拷贝速度较慢并且花销较大。

    1. ![](https://cdn.nlark.com/yuque/0/2021/webp/12535721/1612516619304-385a0d31-4064-4ec5-b063-7c68afe7e7bf.webp#crop=0.0627&crop=0.1178&crop=0.9672&crop=0.9154&height=299&id=tvO8g&originHeight=331&originWidth=670&originalType=binary&ratio=1&rotation=0&showTitle=false&size=0&status=done&style=none&title=&width=606)

    9. 深拷贝的实现

    对于 Student 的引用类型的成员变量 Subject ,需要实现 Cloneable 并重写 clone() 方法。

    1. public class Subject implements Cloneable {
    2. private String name;
    3. public Subject(String name) {
    4. this.name = name;
    5. }
    6. public String getName() {
    7. return name;
    8. }
    9. public void setName(String name) {
    10. this.name = name;
    11. }
    12. @Override
    13. protected Object clone() throws CloneNotSupportedException {
    14. //Subject 如果也有引用类型的成员属性,也应该和 Student 类一样实现
    15. return super.clone();
    16. }
    17. @Override
    18. public String toString() {
    19. return "[Subject: " + this.hashCode() + ",name:" + name + "]";
    20. }
    21. }

    Studentclone() 方法中,需要拿到拷贝自己后产生的新的对象,然后对新的对象的引用类型再调用拷贝操作,实现对引用类型成员变量的深拷贝。

    1. public class Student implements Cloneable {
    2. //引用类型
    3. private Subject subject;
    4. //基础数据类型
    5. private String name;
    6. private int age;
    7. /**
    8. * 重写clone()方法
    9. * @return
    10. */
    11. @Override
    12. public Object clone() {
    13. //深拷贝
    14. try {
    15. // 直接调用父类的clone()方法
    16. Student student = (Student) super.clone();
    17. student.subject = (Subject) subject.clone();
    18. return student;
    19. } catch (CloneNotSupportedException e) {
    20. return null;
    21. }
    22. }
    23. @Override
    24. public String toString() {
    25. return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:"
    26. + name + ",age:" + age + "]";
    27. }
    28. }
    1. public class ShallowCopy {
    2. public static void main(String[] args) {
    3. Subject subject = new Subject("yuwen");
    4. Student studentA = new Student();
    5. studentA.setSubject(subject);
    6. studentA.setName("Lynn");
    7. studentA.setAge(20);
    8. Student studentB = (Student) studentA.clone();
    9. studentB.setName("Lily");
    10. studentB.setAge(18);
    11. Subject subjectB = studentB.getSubject();
    12. subjectB.setName("lishi");
    13. System.out.println("studentA:" + studentA.toString());
    14. System.out.println("studentB:" + studentB.toString());
    15. }
    16. }
    1. studentA:[Student: 460141958,subject:[Subject: 1163157884,name:yuwen],name:Lynn,age:20]
    2. studentB:[Student: 1956725890,subject:[Subject: 356573597,name:lishi],name:Lily,age:18]

    由输出结果可见,深拷贝后,不管是基础数据类型还是引用类型的成员变量,修改其值都不会相互造成影响。

    10. 序列化方式实现深拷贝

    序列化通过将原始对象转化为字节流,再从字节流重建新的 Java 对象,因此原始对象和反序列化后的对象修改互不影响。因此可以使用之前讲到的序列化和反序列化方式来实现深拷贝。

    10.1 自定义序列化工具函数
    1. /**
    2. * JDK序列化方式深拷贝
    3. */
    4. public static <T> T deepClone(T origin) throws IOException, ClassNotFoundException {
    5. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    6. try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);) {
    7. objectOutputStream.writeObject(origin);
    8. objectOutputStream.flush();
    9. }
    10. byte[] bytes = outputStream.toByteArray();
    11. try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);) {
    12. return JdkSerialUtil.readObject(inputStream);
    13. }
    14. }

    10.2 commons-lang3 的序列化工具类

    可以使用 commons-lang3 (3.7 版本)的序列化工具类:
    org.apache.commons.lang3.SerializationUtils#clone

    1. @Test
    2. public void serialUtil() {
    3. Order order = OrderMocker.mock();
    4. // 使用方式
    5. Order cloneOrder = SerializationUtils.clone(order);
    6. assertFalse(order == cloneOrder);
    7. assertFalse(order.getItemList() == cloneOrder.getItemList());
    8. }

    序列化的对象和嵌套的对象都要实现序列化接口Serializable。

    10.3 JSON序列化

    我们还可以通过 JSON 序列化方式实现深拷贝。
    下面我们利用 Google 的 Gson 库(2.8.5 版本),实现基于 JSON 的深拷贝:
    首先我们将深拷贝方法封装到拷贝工具类中:

    1. /**
    2. * Gson方式实现深拷贝
    3. */
    4. public static <T> T deepCloneByGson(T origin, Class<T> clazz) {
    5. Gson gson = new Gson();
    6. return gson.fromJson(gson.toJson(origin), clazz);
    7. }

    使用时直接调用封装的工具方法即可:

    1. @Test
    2. public void withGson() {
    3. Order order = OrderMocker.mock();
    4. // gson序列化方式
    5. Order cloneOrder = CloneUtil.deepCloneByGson(order, Order.class);
    6. assertFalse(order == cloneOrder);
    7. assertFalse(order.getItemList() == cloneOrder.getItemList());
    8. }

    使用 JSON 序列化方式实现深拷贝的好处是,性能比 Java 序列化方式更好,更重要的是不要求序列化对象以及成员属性(嵌套)都要实现序列化接口。