Java 深拷贝,浅拷贝

一直听说这两个词,确实不知道代表啥意思?也不知道究竟要用来做什么?什么时候用到他们。

下面是从一篇博文种得到的解释:

浅复制(浅克隆) :被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。

深复制(深克隆) :被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

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

有两种方式:(目前还不知道怎么做)
1). 实现Cloneable接口并重写Object类中的clone()方法;
2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆;

实现clone方法的步骤
(1)实现Cloneable接口
(2)重载Object类中的clone()方法,重载时需定义为public
(3)在重载方法中,调用super.clone()

 1 package lesson1211;
 2 
 3 public class Student implements Cloneable {   //不实现Cloneable接口,编译不会报错,但是运行时会报异常,所以必须实现Cloneable接口
 4     private int number;
 5 
 6     //浅复制
 7     /* (non-Javadoc)
 8      * @see java.lang.Object#clone()
 9      */
10     @Override
11     protected Object clone(){
12         
13         Student stu = null;        
14         try {
15             stu = (Student)super.clone();
16         } catch (CloneNotSupportedException e) {
17             // TODO Auto-generated catch block
18             e.printStackTrace();
19         }        
20         return stu;
21     }
22     
23     /**
24      * @return the number
25      */
26     public int getNumber() {
27         return number;
28     }
29 
30     /**
31      * @param number the number to set
32      */
33     public void setNumber(int number) {
34         this.number = number;
35     }
36 }
37 
38 package lesson1211;
39 
40 public class TestClone {
41 
42     public static void main(String[] args) {
43         Student stu1 = new Student();
44         stu1.setNumber(12345);
45         Student stu2 = (Student)stu1.clone();
46         System.out.println("stu1: " + stu1.getNumber());
47         System.out.println("stu2: " + stu2.getNumber());
48         
49         stu2.setNumber(45678);
50         
51         System.out.println("stu1: " + stu1.getNumber());
52         System.out.println("stu2: " + stu2.getNumber());53         
54     } 
57 }

解释:
(1)clone()方法是定义在java.lang.Object类中,该方法是一个protected的方法,所以重载时要把clone()方法的属性设置为public,这样其它类才能调用这个clone类的clone()方法
(2)实现Cloneable接口:Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出 CloneNotSupportedException异常。

浅克隆

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
这里写图片描述
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。    可以看下,原型对象的成员变量是引用类型Car,确实指向同一地址,没有新城新的引用。

  1 package lesson1211;
  2 
  3 public class Student implements Cloneable {
  4     private int number;
  5     private Car car;
  6     
  7     public Student(int number, Car car) {
  8         
  9         this.car = car;
 10         this.number = number;
 11     }
 12     
 13     //浅复制
 14     /* (non-Javadoc)
 15      * @see java.lang.Object#clone()
 16      */
 17     @Override
 18     protected Object clone(){
 19         
 20         Student stu = null;        
 21         try {
 22             stu = (Student)super.clone();
 23         } catch (CloneNotSupportedException e) {
 24             // TODO Auto-generated catch block
 25             e.printStackTrace();
 26         }        
 27         return stu;
 28     }
 29     
 30     /**
 31      * @return the number
 32      */
 33     public int getNumber() {
 34         return number;
 35     }
 36 
 37     /**
 38      * @param number the number to set
 39      */
 40     public void setNumber(int number) {
 41         this.number = number;
 42     }
 43 
 44     /**
 45      * @return the car
 46      */
 47     public Car getCar() {
 48         return car;
 49     }
 50 
 51     /**
 52      * @param car the car to set
 53      */
 54     public void setCar(Car car) {
 55         this.car = car;
 56     }    
 57 }
 58 
 59 package lesson1211;
 60 
 61 public class Car {
 62     
 63     private String name;
 64     
 65     private int speed;
 66     
 67     public Car(String name, int speed) {
 68         this.name = name;
 69         this.speed = speed;
 70     }
 71 
 72     /**
 73      * @return the name
 74      */
 75     public String getName() {
 76         return name;
 77     }
 78 
 79     /**
 80      * @param name the name to set
 81      */
 82     public void setName(String name) {
 83         this.name = name;
 84     }
 85 
 86     /**
 87      * @return the speed
 88      */
 89     public int getSpeed() {
 90         return speed;
 91     }
 92 
 93     /**
 94      * @param speed the speed to set
 95      */
 96     public void setSpeed(int speed) {
 97         this.speed = speed;
 98     }
 99     
100 }
101 package lesson1211;
102 
103 public class TestClone {
104 
105     public static void main(String[] args) {
106         Car car = new Car(null,20000);
107         Student stu1 = new Student(3,car);
108         stu1.setNumber(12345);
109         stu1.getCar().setName("Baoma");
110         
111         Student stu2 = (Student)stu1.clone();
112         System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getCar().getName());
113         System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getCar().getName());
114         
115         stu2.setNumber(45678);
116         stu2.getCar().setName("benchi");
117         
118         System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getCar().getName());
119         System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getCar().getName());120         
121     } 
124 }

运行结果是:

stu1: 12345 Baoma
stu2: 12345 Baoma
stu1: 12345 benchi   //修改stu2的car类型,stu1里面也会变,说明stu1和stu2里面的引用car指向同一个地址。这就是浅复制。
stu2: 45678 benchi

深克隆

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

在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

同样是上面的例子,我们怎么来通过Object类的clone来实现深克隆,code如下:

  1 package lesson1211;
  2 
  3 public class Student implements Cloneable {
  4     private int number;
  5     String name;
  6     private Car car;
  7     
  8     public Student(int number, String name, Car car) {
  9         
 10         this.car = car;
 11         this.name = name;
 12         this.number = number;
 13     }
 14     
 15     //浅复制
 16     /* (non-Javadoc)
 17      * @see java.lang.Object#clone()
 18      */
 19     @Override
 20     protected Object clone(){
 21         
 22         Student stu = null;    
 23         try {
 24             stu = (Student)super.clone(); //浅复制            
 25             stu.car = (Car)car.clone();   //深度复制
 26             
 27         } catch (CloneNotSupportedException e) {
 28             // TODO Auto-generated catch block
 29             e.printStackTrace();
 30         }        
 31         return stu;
 32     }
 33     
 34     /**
 35      * @return the number
 36      */
 37     public int getNumber() {
 38         return number;
 39     }
 40 
 41     /**
 42      * @param number the number to set
 43      */
 44     public void setNumber(int number) {
 45         this.number = number;
 46     }
 47 
 48     /**
 49      * @return the car
 50      */
 51     public Car getCar() {
 52         return car;
 53     }
 54 
 55     /**
 56      * @param car the car to set
 57      */
 58     public void setCar(Car car) {
 59         this.car = car;
 60     }
 61 
 62     /**
 63      * @return the name
 64      */
 65     public String getName() {
 66         return name;
 67     }
 68 
 69     /**
 70      * @param name the name to set
 71      */
 72     public void setName(String name) {
 73         this.name = name;
 74     }    
 75 }
 76 
 77 package lesson1211;
 78 
 79 public class Car implements Cloneable {
 80     
 81     private String name;
 82     
 83     private int speed;
 84     
 85     public Car(String name, int speed) {
 86         this.name = name;
 87         this.speed = speed;
 88     }
 89 
 90     /**
 91      * @return the name
 92      */
 93     public String getName() {
 94         return name;
 95     }
 96 
 97     /**
 98      * @param name the name to set
 99      */
100     public void setName(String name) {
101         this.name = name;
102     }
103 
104     /**
105      * @return the speed
106      */
107     public int getSpeed() {
108         return speed;
109     }
110 
111     /**
112      * @param speed the speed to set
113      */
114     public void setSpeed(int speed) {
115         this.speed = speed;
116     }
117 
118     /* (non-Javadoc)
119      * @see java.lang.Object#clone()
120      */
121     @Override
122     public Object clone() {        
123         Car car = null;        
124         try {
125             car =  (Car)super.clone();
126         } catch (CloneNotSupportedException e) {
127             // TODO Auto-generated catch block
128             e.printStackTrace();
129         }        
130         return car;         
131     }    
132 }
133 
134 package lesson1211;
135 
136 public class TestClone {
137 
138     public static void main(String[] args) {
139         Car car = new Car(null,20000);
140         Student stu1 = new Student(3,null, car);
141         stu1.setNumber(12345);
142         stu1.setName("zhangsan");
143         stu1.getCar().setName("Baoma");
144         
145         Student stu2 = (Student)stu1.clone();
146         System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getName()+ " " + stu1.getCar().getName());
147         System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getName()+ " " + stu2.getCar().getName());
148         
149         stu2.setNumber(45678);
150         stu2.setName("zhaosi");
151         stu2.getCar().setName("benchi");
152         
153         System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getName()+ " " + stu1.getCar().getName());
154         System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getName()+ " " + stu2.getCar().getName());
155         
156     }
157 }

运行结果:

stu1: 12345 zhangsan Baoma
stu2: 12345 zhangsan Baoma
stu1: 12345 zhangsan Baoma
stu2: 45678 zhaosi benchi      //达到了我们想要的深度复制结果。

code主要修改的是Car类,它也必须实现Cloneable接口。   Student在实现clone时,单独在clone 它的引用类型变量。  在student里面添加一个String变量,发现是深克隆。

除了基本数据类型能自动实现深度clone以外,String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。

JDK中StringBuffer类型,关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个 final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和 StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是 SringBuffer对象,而且变量名仍是p): o.p = new StringBuffer(p.toString()); //原来的是:stu.car = (Car)car.clone();

通过以上我们可以看出在某些情况下,我们可以利用clone方法来实现对象的深度复制,但对于比较复杂的对象(比如对象中包含其他对象,其他对象又包含别的对象…..)这样我们必须进行层层深度clone,每个对象需要实现cloneable接口,比较麻烦,那就继续学习下一个序列化方法。

 利用串行化来做深复制

所谓对象序列化就是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。

也许你会说,只了解一点点,但从来没有接触过,其实未必如此。RMI、Socket、JMS、EJB你总该用过一种吧,彼此为什么能够传递Java对象,当然都是对象序列化机制的功劳。

第一次使用Java的对象序列化是做某项目,当时要求把几棵非常复杂的树(JTree)及相应的数据保存下来(就是我们常用的保存功能),以便下次运行程序时可以继续上次的操作。

那时XML技术在网上非常的热,而且功能也强大,再加上树的结构本来就和XML存储数据的格式很像。作为一项对新技术比较有兴趣的我当然很想尝试一下。不过经过仔细分析,发现如果采用XML保存数据,后果真是难以想象:哪棵树的哪个节点被展开、展开到第几级、节点当前的属性是什么。真是不知该用A、B、C还是用1、2、3来表示。

还好,发现了Java的对象序列化机制,问题迎刃而解,只需简单的将每棵树的根节点序列化保存到硬盘上,下次再通过反序列化后的根节点就可以轻松的构造出和原来一模一样的树来。

其实保存数据,尤其是复杂数据的保存正是对象序列化的典型应用。最近另一个项目就遇到了需要对非常复杂的数据进行存取,通过使用对象的序列化,问题同样化难为简。

上面这段是摘抄于帖子:https://blog.csdn.net/pony_maggie/article/details/52091588,我自己目前当然没做过。

对象的序列化还有另一个容易被大家忽略的功能就是对象复制(Clone),Java中通过Clone机制可以复制大部分的对象,但是众所周知,Clone有深层Clone和浅层Clone,如果你的对象非常非常复杂,假设有个100层的Collection(夸张了点),如果你想实现深层 Clone,真是不敢想象,如果使用序列化,不会超过10行代码就可以解决。

public Object deepClone() {    
   //将对象写到流里    
   ByteArrayOutoutStream bo=new ByteArrayOutputStream();    
   ObjectOutputStream oo=new ObjectOutputStream(bo);    
   oo.writeObject(this);    
   //从流里读出来    
   ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());    
   ObjectInputStream oi=new ObjectInputStream(bi);    
   return(oi.readObject());    
}
这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,
就需要仔细考察那些不可串行化的对象或属性可否设成transient,从而将之排除在复制过程之外。 上例代码修改如下:
  1 package lesson1211;
  2 
  3 import java.io.ByteArrayInputStream;
  4 import java.io.ByteArrayOutputStream;
  5 import java.io.IOException;
  6 import java.io.ObjectInputStream;
  7 import java.io.ObjectOutputStream;
  8 import java.io.Serializable;
  9 
 10 public class Student implements Serializable{
 11     private int number;
 12     String name;
 13     private Car car;
 14     
 15     public Student(int number, String name, Car car) {
 16         
 17         this.car = car;
 18         this.name = name;
 19         this.number = number;
 20     }
 21     
 22     public Object deepClone() throws IOException, ClassNotFoundException{
 23         ByteArrayOutputStream bo = new ByteArrayOutputStream();
 24         ObjectOutputStream oo = new ObjectOutputStream(bo);
 25         oo.writeObject(this);
 26         
 27         ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
 28         ObjectInputStream oi = new ObjectInputStream(bi);
 29         return oi.readObject();        
 30         
 31     }
 32     
 33     /**
 34      * @return the number
 35      */
 36     public int getNumber() {
 37         return number;
 38     }
 39 
 40     /**
 41      * @param number the number to set
 42      */
 43     public void setNumber(int number) {
 44         this.number = number;
 45     }
 46 
 47     /**
 48      * @return the car
 49      */
 50     public Car getCar() {
 51         return car;
 52     }
 53 
 54     /**
 55      * @param car the car to set
 56      */
 57     public void setCar(Car car) {
 58         this.car = car;
 59     }
 60 
 61     /**
 62      * @return the name
 63      */
 64     public String getName() {
 65         return name;
 66     }
 67 
 68     /**
 69      * @param name the name to set
 70      */
 71     public void setName(String name) {
 72         this.name = name;
 73     }    
 74 }
 75 
 76 package lesson1211;
 77 
 78 import java.io.Serializable;
 79 
 80 public class Car implements Serializable {
 81     
 82     private String name;
 83     
 84     private int speed;
 85     
 86     public Car(String name, int speed) {
 87         this.name = name;
 88         this.speed = speed;
 89     }
 90 
 91     /**
 92      * @return the name
 93      */
 94     public String getName() {
 95         return name;
 96     }
 97 
 98     /**
 99      * @param name the name to set
100      */
101     public void setName(String name) {
102         this.name = name;
103     }
104 
105     /**
106      * @return the speed
107      */
108     public int getSpeed() {
109         return speed;
110     }
111 
112     /**
113      * @param speed the speed to set
114      */
115     public void setSpeed(int speed) {
116         this.speed = speed;
117     }
118     
119 }
120 
121 package lesson1211;
122 
123 import java.io.IOException;
124 
125 public class TestClone {
126 
127     public static void main(String[] args) throws IOException, ClassNotFoundException{
128         Car car = new Car(null,20000);
129         Student stu1 = new Student(3,null, car);
130         stu1.setNumber(12345);
131         stu1.setName("zhangsan");
132         stu1.getCar().setName("Baoma");
133         
134         //Student stu2 = (Student)stu1.clone();
135         Student stu2 = (Student)stu1.deepClone();
136         System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getName()+ " " + stu1.getCar().getName());
137         System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getName()+ " " + stu2.getCar().getName());
138         
139         stu2.setNumber(45678);
140         stu2.setName("zhaosi");
141         stu2.getCar().setName("benchi");
142         
143         System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getName()+ " " + stu1.getCar().getName());
144         System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getName()+ " " + stu2.getCar().getName());
145         
146     }
147     
148 
149 }

运行结果:

stu1: 12345 zhangsan Baoma
stu2: 12345 zhangsan Baoma
stu1: 12345 zhangsan Baoma
stu2: 45678 zhaosi benchi
确实也实现了深度复制。看上去感觉比clone还简单

两个问题:1 引用类型类可不可以不实现Serializable?例如Car类不实现Serializable?    不可以,必须实现,否则会抛异常

2:如果引用类型是transient,就不能进行序列化?   尝试将private Car car;改为private transient Car car;  在上面例子中会报错,因为序列化进去的Car是null, 后面getCar.getName()会报空指针的。对于没有实现Serializable接口的,序列化时要主动将其定义为transient,只要后面不在调用这种类引用就可以。

虽然Java的序列化非常简单、强大,但是要用好,还有很多地方需要注意。比如曾经序列化了一个对象,可由于某种原因,该类做了一点点改动,然后重新被编译,那么这时反序列化刚才的对象,将会出现异常。

你可以通过添加serialVersionUID属性来解决这个问题。如果你的类是个单态(Singleton)类,是否允许用户通过序列化机制复制该类,如果不允许你需要谨慎对待该类的实现。

https://blog.csdn.net/pony_maggie/article/details/52091588

https://blog.csdn.net/w410589502/article/details/54985987

https://blog.csdn.net/tounaobun/article/details/8491392

https://www.cnblogs.com/dolphin0520/p/3700693.html

原文地址:https://www.cnblogs.com/beilou310/p/10094782.html