• 面试题之Java的克隆


    Java的克隆

    • 在 Object 基类中,有一个clone()方法,克隆对象是原对象的拷贝。

      • clone()方法是 Object 类的,并不是 Cloneable 接口的,Cloneable 只是一个标记接口,如果没有实现 Cloneable 接口,那么调用clone()方法就会爆出 CloneNotSupportedException 异常。
      • Object 类中的 clone()方法是 protected 修饰的,所以我们要在子类中重写这个方法,并且设置成 public,才能在子类外部进行访问。
      • clone() 方法是 native 方法,native 方法的效率远高于非 native 方法,因此如果我们需要拷贝一个对象,建议使用 clone(),而不是 new。
      • 返回值是一个Object对象,因此通过clone方法克隆一个对象,需要强制转换
      protected native Object clone() throws CloneNotSupportedException;
      
      • 1
    • 在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝一份,而浅克隆仅仅是拷贝一份此属性的引用

    浅克隆
    • 在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。也就是说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

    • 实现步骤:

      1. 被克隆的类实现Cloneable接口
      2. 覆写clone()方法
      public class ShallowCloneTest {
          public static void main(String[] args) throws CloneNotSupportedException {
              Food food = new Food("梅菜扣肉", 15);
              Person person = new Person("zs", 16, food);
              Person person2 = (Person) person.clone();
              System.out.println(person.getName()  + "," + person.getAge());
              System.out.println(person2.getName()  + "," + person2.getAge());
              System.out.println(person.getFood());
              System.out.println(person2.getFood());
          }
      }
      
      class Person implements Cloneable {
          private String name;
          private Integer age;
          private Food food;
      
          public Person(String name, Integer age, Food food) {
              this.name = name;
              this.age = age;
              this.food = food;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public Integer getAge() {
              return age;
          }
      
          public void setAge(Integer age) {
              this.age = age;
          }
      
          public Food getFood() {
              return food;
          }
      
          public void setFood(Food food) {
              this.food = food;
          }
      
          @Override
          public Object clone() throws CloneNotSupportedException {
              return super.clone();
          }
      }
      
      class Food {
          private String name;
          private Integer price;
      
          public Food(String name, Integer price) {
              this.name = name;
              this.price = price;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public Integer getPrice() {
              return price;
          }
      
          public void setPrice(Integer price) {
              this.price = price;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
    • 输出结果:可以看到 person2 复制了 person 的值类型成员变量 name 和 age,但是 person2 对 person 中的引用类型成员变量 food 只是进行了地址的复制,所有两个对象的food属性都是指向同一个地址。


      .

    深克隆
    • 在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。也就是说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

    实现深克隆方式一:实现Cloneable接口
    • 可以通过覆盖 Object 类的 clone() 方法并进行递归调用实现

      public class DeepCloneTest01 {
          public static void main(String[] args) throws CloneNotSupportedException {
              Food food = new Food("梅菜扣肉", 15);
              Person person = new Person("zs", 16, food);
              Person person2 = (Person) person.clone();
              System.out.println(person.getName()  + "," + person.getAge());
              System.out.println(person2.getName()  + "," + person2.getAge());
              System.out.println(person.getFood());
              System.out.println(person2.getFood());
          }
      }
      
      class Person implements Cloneable {
          private String name;
          private Integer age;
          private Food food;
      
          public Person(String name, Integer age, Food food) {
              this.name = name;
              this.age = age;
              this.food = food;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public Integer getAge() {
              return age;
          }
      
          public void setAge(Integer age) {
              this.age = age;
          }
      
          public Food getFood() {
              return food;
          }
      
          public void setFood(Food food) {
              this.food = food;
          }
      
          @Override
          public Object clone() throws CloneNotSupportedException {
      
              Person person = (Person) super.clone();
              person.food = (Food) this.food.clone();
              return person;
          }
      }
      
      class Food implements Cloneable{
          private String name;
          private Integer price;
      
          public Food(String name, Integer price) {
              this.name = name;
              this.price = price;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public Integer getPrice() {
              return price;
          }
      
          public void setPrice(Integer price) {
              this.price = price;
          }
      
          @Override
          public Object clone() throws CloneNotSupportedException {
              return super.clone();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
    • 输出结果:可以看到,深克隆不仅对值类型成员变量进行了拷贝,对引用类型的成员变量也拷贝了一份,而不是简单的地址复制。


      .

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

      public class Test05 {
          public static void main(String[] args) {
              Food01 food01 = new Food01("梅菜扣肉", 15);
              Person01 person = new Person01("zs", 16, food01);
              Person01 person2 = (Person01) person.myclone();
              System.out.println(person.getName()  + "," + person.getAge());
              System.out.println(person2.getName()  + "," + person2.getAge());
              System.out.println(person.getFood());
              System.out.println(person2.getFood());
          }
      }
      
      class Person01 implements Serializable {
          //显式声明序列化ID
          private static final long serialVersionUID = 369285298572941L;
      
          private String name;
          private Integer age;
          private Food01 food;
          
          public Person01 myclone() {
              Person01 person01 = null;
              //写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
              try {
                  ByteArrayOutputStream baos = new ByteArrayOutputStream();
                  ObjectOutputStream oos = new ObjectOutputStream(baos);
                  oos.writeObject(this);
                  // 将流序列化成对象
                  ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                  ObjectInputStream ois = new ObjectInputStream(bais);
                  person01 = (Person01) ois.readObject();
              } catch (IOException e) {
                  e.printStackTrace();
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              }
              return person01;
          }
      
          public Person01(String name, Integer age, Food01 food) {
              this.name = name;
              this.age = age;
              this.food = food;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public Integer getAge() {
              return age;
          }
      
          public void setAge(Integer age) {
              this.age = age;
          }
      
          public Food01 getFood() {
              return food;
          }
      
          public void setFood(Food01 food) {
              this.food = food;
          }
      }
      
      class Food01 implements Serializable{
          private String name;
          private Integer price;
      
          public Food01(String name, Integer price) {
              this.name = name;
              this.price = price;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public Integer getPrice() {
              return price;
          }
      
          public void setPrice(Integer price) {
              this.price = price;
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
    • 输出结果:


      .

    • 注意:

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

  • 相关阅读:
    如何公网远程访问本地群晖NAS File Station文件夹
    【项目总结】C++ 云盘
    mysql中的正则表达式的用法
    HTML知识点
    2022年Java秋招面试必看的 | ZooKeeper面试题
    网络安全阶段一学习笔记
    【一起学Rust | 进阶篇 | jni库】JNI实现Java与Rust进行交互
    Linux下库的入门与制作
    LeetCode(16)接雨水【数组/字符串】【困难】
    如何看待PMP的2022年11月新考纲?
  • 原文地址:https://blog.csdn.net/qq_52248567/article/details/126616498