浅克隆和深克隆

在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制。

1、浅克隆

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

对象内有多个对象型的成员变量怎么实现深克隆

在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆

浅克隆的实现:

1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

2. 覆盖clone()方法,访问修饰符设为public方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)

对象内有多个对象型的成员变量怎么实现深克隆
class Student implements Cloneable{  
    private int number;  
  
    public int getNumber() {  
        return number;  
    }  
  
    public void setNumber(int number) {  
        this.number = number;  
    }  
      
    @Override  
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();  
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return stu;  
    }  
}  
public class Test {  
    public static void main(String args[]) {  
        Student stu1 = new Student();  
        stu1.setNumber(12345);  
        Student stu2 = (Student)stu1.clone();  
          
        System.out.println("学生1:" + stu1.getNumber());  
        System.out.println("学生2:" + stu2.getNumber());  
          
        stu2.setNumber(54321);  
      
        System.out.println("学生1:" + stu1.getNumber());  
        System.out.println("学生2:" + stu2.getNumber());  
    }  
}  
对象内有多个对象型的成员变量怎么实现深克隆

结果:

学生1:12345  

学生2:12345  

学生1:12345  

学生2:54321

如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:

System.out.println(stu1 == stu2); // false  

在来看看学生类有引用类型的情况,我们在学生类里再加一个Address类。

对象内有多个对象型的成员变量怎么实现深克隆
 1 class Address  {  
 2     private String add;  
 3   
 4     public String getAdd() {  
 5         return add;  
 6     }  
 7   
 8     public void setAdd(String add) {  
 9         this.add = add;  
10     }  
11       
12 }  
13   
14 class Student implements Cloneable{  
15     private int number;  
16   
17     private Address addr;  
18       
19     public Address getAddr() {  
20         return addr;  
21     }  
22   
23     public void setAddr(Address addr) {  
24         this.addr = addr;  
25     }  
26   
27     public int getNumber() {  
28         return number;  
29     }  
30   
31     public void setNumber(int number) {  
32         this.number = number;  
33     }  
34       
35     @Override  
36     public Object clone() {  
37         Student stu = null;  
38         try{  
39             stu = (Student)super.clone();  
40         }catch(CloneNotSupportedException e) {  
41             e.printStackTrace();  
42         }  
43         return stu;  
44     }  
45 }  
46 public class Test {  
47       
48     public static void main(String args[]) {  
49           
50         Address addr = new Address();  
51         addr.setAdd("杭州市");  
52         Student stu1 = new Student();  
53         stu1.setNumber(123);  
54         stu1.setAddr(addr);  
55           
56         Student stu2 = (Student)stu1.clone();  
57           
58         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
59         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
60     }  
61 }  
对象内有多个对象型的成员变量怎么实现深克隆

结果:

学生1:123,地址:杭州市  

学生2:123,地址:杭州市  

 

乍一看没什么问题,真的是这样吗?

我们在main方法中试着改变addr实例的地址。

addr.setAdd("西湖区");  
  
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  

结果:

学生1:123,地址:杭州市  
学生2:123,地址:杭州市  
学生1:123,地址:西湖区  
学生2:123,地址:西湖区  

这就奇怪了,怎么两个学生的地址都改变了?

原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

上面的复制被称为浅克隆。

2、深克隆

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

对象内有多个对象型的成员变量怎么实现深克隆

 

深克隆有两种实现方式:

1)基于浅克隆的基础通过覆盖Object类的clone()方法实现。

为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:

对象内有多个对象型的成员变量怎么实现深克隆
 1 package abc;  
 2   
 3 class Address implements Cloneable {  
 4     private String add;  
 5   
 6     public String getAdd() {  
 7         return add;  
 8     }  
 9   
10     public void setAdd(String add) {  
11         this.add = add;  
12     }  
13       
14     @Override  
15     public Object clone() {  
16         Address addr = null;  
17         try{  
18             addr = (Address)super.clone();  
19         }catch(CloneNotSupportedException e) {  
20             e.printStackTrace();  
21         }  
22         return addr;  
23     }  
24 }  
25   
26 class Student implements Cloneable{  
27     private int number;  
28   
29     private Address addr;  
30       
31     public Address getAddr() {  
32         return addr;  
33     }  
34   
35     public void setAddr(Address addr) {  
36         this.addr = addr;  
37     }  
38   
39     public int getNumber() {  
40         return number;  
41     }  
42   
43     public void setNumber(int number) {  
44         this.number = number;  
45     }  
46       
47     @Override  
48     public Object clone() {  
49         Student stu = null;  
50         try{  
51             stu = (Student)super.clone();   //浅复制  
52         }catch(CloneNotSupportedException e) {  
53             e.printStackTrace();  
54         }  
55         stu.addr = (Address)addr.clone();   //深度复制  
56         return stu;  
57     }  
58 }  
59 public class Test {  
60       
61     public static void main(String args[]) {  
62           
63         Address addr = new Address();  
64         addr.setAdd("杭州市");  
65         Student stu1 = new Student();  
66         stu1.setNumber(123);  
67         stu1.setAddr(addr);  
68           
69         Student stu2 = (Student)stu1.clone();  
70           
71         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
72         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
73           
74         addr.setAdd("西湖区");  
75           
76         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
77         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
78     }  
79 }  
对象内有多个对象型的成员变量怎么实现深克隆

结果:

学生1:123,地址:杭州市  
学生2:123,地址:杭州市  
学生1:123,地址:西湖区  
学生2:123,地址:杭州市  

2)通过序列化(Serialization)的方式(可解决多层克隆的问题)。

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

序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等

public class Student implements Serializable{
  private String name;
  public Address address;
 //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] 
  public Student myclone() {
      Student stu = 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);
          stu = (Student) ois.readObject();
      } catch (IOException e) {
          e.printStackTrace();
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
      return stu;
  }
}
Address也必须实现Serializable,否则无法序列化
public class Address implements Serializable{
  private String add;

public Address(String add) { this.add= add; } }

将不需要序列化的属性前添加关键字 transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

被修饰static的属性也不可以序列化 ,序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。

总结

实现对象克隆有两种方式:

  1). 实现Cloneable接口并重写Object类中的clone()方法;

  2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

                             注意

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

相关文章:

  • 2022-03-01
  • 2021-12-23
  • 2022-02-23
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2021-06-18
  • 2022-12-23
  • 2022-12-23
  • 2021-05-17
  • 2021-06-01
  • 2021-06-02
相关资源
相似解决方案