zoukankan      html  css  js  c++  java
  • 011 深拷贝和浅拷贝

    原文链接:

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

    假如说你想复制一个简单变量。很简单:

    1. int apples = 5; 
    2. int pears = apples; 
    
    

    不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

    但是如果你复制的是一个对象,情况就有些复杂了。

    假设说我是一个beginner,我会这样写:

    1. class Student { 
    2.     private int number; 
    3.  
    4.     public int getNumber() { 
    5.         return number; 
    6.     } 
    7.  
    8.     public void setNumber(int number) { 
    9.         this.number = number; 
    10.     } 
    11.      
    12. public class Test { 
    13.      
    14.     public static void main(String args[]) { 
    15.          
    16.         Student stu1 = new Student(); 
    17.         stu1.setNumber(12345); 
    18.         Student stu2 = stu1; 
    19.          
    20.         System.out.println("学生1:" + stu1.getNumber()); 
    21.         System.out.println("学生2:" + stu2.getNumber()); 
    22.     } 
    
    
    

    打印结果:

    1. 学生1:12345 
    2. 学生2:12345 
    
    
    

    这里我们自定义了一个学生类,该类只有一个number字段。

    我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)

    再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,

    难道真的是这样吗?

    我们试着改变stu2实例的number字段,再打印结果看看:

    1. stu2.setNumber(54321); 
    2.  
    3. System.out.println("学生1:" + stu1.getNumber()); 
    4. System.out.println("学生2:" + stu2.getNumber()); 
    		stu2.setNumber(54321);
    	
    		System.out.println("学生1:" + stu1.getNumber());
    		System.out.println("学生2:" + stu2.getNumber());

    打印结果:

    1. 学生1:54321 
    2. 学生2:54321 

    这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?

    原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,

    这样,stu1和stu2指向内存堆中同一个对象。如图:

    那么,怎样才能达到复制一个对象呢?

    是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。

    该方法的签名是:

    protected native Object clone() throws CloneNotSupportedException;

    因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。

    要想对一个对象进行复制,就需要对clone方法覆盖。

    一般步骤是(浅复制):

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

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

    下面对上面那个方法进行改造:

    1. class Student implements Cloneable{ 
    2.     private int number; 
    3.  
    4.     public int getNumber() { 
    5.         return number; 
    6.     } 
    7.  
    8.     public void setNumber(int number) { 
    9.         this.number = number; 
    10.     } 
    11.      
    12.     @Override 
    13.     public Object clone() { 
    14.         Student stu = null; 
    15.         try{ 
    16.             stu = (Student)super.clone(); 
    17.         }catch(CloneNotSupportedException e) { 
    18.             e.printStackTrace(); 
    19.         } 
    20.         return stu; 
    21.     } 
    22. public class Test { 
    23.      
    24.     public static void main(String args[]) { 
    25.          
    26.         Student stu1 = new Student(); 
    27.         stu1.setNumber(12345); 
    28.         Student stu2 = (Student)stu1.clone(); 
    29.          
    30.         System.out.println("学生1:" + stu1.getNumber()); 
    31.         System.out.println("学生2:" + stu2.getNumber()); 
    32.          
    33.         stu2.setNumber(54321); 
    34.      
    35.         System.out.println("学生1:" + stu1.getNumber()); 
    36.         System.out.println("学生2:" + stu2.getNumber()); 
    37.     } 
    
    

    打印结果:

    1. 学生1:12345 
    2. 学生2:12345 
    3. 学生1:12345 
    4. 学生2:54321 

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

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

    上面的复制被称为浅复制(Shallow Copy),还有一种稍微复杂的深度复制(deep copy):

    我们在学生类里再加一个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. class Student implements Cloneable{ 
    14.     private int number; 
    15.  
    16.     private Address addr; 
    17.      
    18.     public Address getAddr() { 
    19.         return addr; 
    20.     } 
    21.  
    22.     public void setAddr(Address addr) { 
    23.         this.addr = addr; 
    24.     } 
    25.  
    26.     public int getNumber() { 
    27.         return number; 
    28.     } 
    29.  
    30.     public void setNumber(int number) { 
    31.         this.number = number; 
    32.     } 
    33.      
    34.     @Override 
    35.     public Object clone() { 
    36.         Student stu = null; 
    37.         try{ 
    38.             stu = (Student)super.clone(); 
    39.         }catch(CloneNotSupportedException e) { 
    40.             e.printStackTrace(); 
    41.         } 
    42.         return stu; 
    43.     } 
    44. public class Test { 
    45.      
    46.     public static void main(String args[]) { 
    47.          
    48.         Address addr = new Address(); 
    49.         addr.setAdd("杭州市"); 
    50.         Student stu1 = new Student(); 
    51.         stu1.setNumber(123); 
    52.         stu1.setAddr(addr); 
    53.          
    54.         Student stu2 = (Student)stu1.clone(); 
    55.          
    56.         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); 
    57.         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 
    58.     } 
    
    

    打印结果:

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

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

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

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

    打印结果:

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

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

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

    所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将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. class Student implements Cloneable{ 
    26.     private int number; 
    27.  
    28.     private Address addr; 
    29.      
    30.     public Address getAddr() { 
    31.         return addr; 
    32.     } 
    33.  
    34.     public void setAddr(Address addr) { 
    35.         this.addr = addr; 
    36.     } 
    37.  
    38.     public int getNumber() { 
    39.         return number; 
    40.     } 
    41.  
    42.     public void setNumber(int number) { 
    43.         this.number = number; 
    44.     } 
    45.      
    46.     @Override 
    47.     public Object clone() { 
    48.         Student stu = null; 
    49.         try{ 
    50.             stu = (Student)super.clone();   //浅复制  
    51.         }catch(CloneNotSupportedException e) { 
    52.             e.printStackTrace(); 
    53.         } 
    54.         stu.addr = (Address)addr.clone();   //深度复制  
    55.         return stu; 
    56.     } 
    57. public class Test { 
    58.      
    59.     public static void main(String args[]) { 
    60.          
    61.         Address addr = new Address(); 
    62.         addr.setAdd("杭州市"); 
    63.         Student stu1 = new Student(); 
    64.         stu1.setNumber(123); 
    65.         stu1.setAddr(addr); 
    66.          
    67.         Student stu2 = (Student)stu1.clone(); 
    68.          
    69.         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); 
    70.         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 
    71.          
    72.         addr.setAdd("西湖区"); 
    73.          
    74.         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); 
    75.         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 
    76.     } 
    
    

    打印结果:

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

    这样结果就符合我们的想法了。

    总结:浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝,

    没有对引用指向的对象进行拷贝。

    而深拷贝是指在拷贝对象时,同时会对引用指向的对象进行拷贝。

    区别就在于是否对  对象中的引用变量所指向的对象进行拷贝。

    最后我们可以看看API里其中一个实现了clone方法的类:

    java.util.Date:

    1. /**
    2. * Return a copy of this object.
    3. */ 
    4. public Object clone() { 
    5.     Date d = null; 
    6.     try { 
    7.         d = (Date)super.clone(); 
    8.         if (cdate != null) { 
    9.             d.cdate = (BaseCalendar.Date) cdate.clone(); 
    10.         } 
    11.     } catch (CloneNotSupportedException e) {} // Won't happen  
    12.     return d; 
     

    该类其实也属于深度复制。

      

    作者:海子
             
    本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    poj3669 广搜
    检索所有课程都选修的的学生的学号与姓名
    UVA10160 Servicing Stations
    uva11205 The broken pedometer 子集生成
    poj1101 the game 广搜
    poj3009 Curling 2.0 深搜
    poj 1564 Sum It Up 搜索
    HDU 2268 How To Use The Car (数学题)
    codeforces 467C George and Job(简单dp,看了题解抄一遍)
    HDU 2267 How Many People Can Survive(广搜,简单)
  • 原文地址:https://www.cnblogs.com/mu-tou-man/p/4552209.html
Copyright © 2011-2022 走看看