Java 深拷贝,浅拷贝

2021年11月24日 阅读数:2
这篇文章主要向大家介绍Java 深拷贝,浅拷贝,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

一直据说这两个词,确实不知道表明啥意思?也不知道究竟要用来作什么?何时用到他们。html

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

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

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

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

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

实现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异常。this

浅克隆spa

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

  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仍是用一、二、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