浅析Java中的深拷贝和浅拷贝

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

[java] view plaincopyprint?

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

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

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

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

[java] view plaincopyprint?

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. } 
13. public class Test {  
14.      
15. public static void main(String args[]) {  
16.          
17. new Student();  
18. 12345);  
19.         Student stu2 = stu1; 
20.          
21. "学生1:" + stu1.getNumber());  
22. "学生2:" + stu2.getNumber());  
23.     } 
24. } 
打印结果:
打印结果:

[plain] view plaincopyprint?

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

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

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

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

    难道真的是这样吗?

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

    [java] view plaincopyprint?

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

      打印结果: 

      [plain] view plaincopyprint?

      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为本地方法)

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

      [java] view plaincopyprint?

      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. null;  
      15. try{  
      16. super.clone();  
      17. catch(CloneNotSupportedException e) {  
      18.             e.printStackTrace(); 
      19.         } 
      20. return stu;  
      21.     } 
      22. } 
      23. public class Test {  
      24.      
      25. public static void main(String args[]) {  
      26.          
      27. new Student();  
      28. 12345);  
      29.         Student stu2 = (Student)stu1.clone(); 
      30.          
      31. "学生1:" + stu1.getNumber());  
      32. "学生2:" + stu2.getNumber());  
      33.          
      34. 54321);  
      35.      
      36. "学生1:" + stu1.getNumber());  
      37. "学生2:" + stu2.getNumber());  
      38.     } 
      39. }

      打印结果:

      [plain] view plaincopyprint?

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

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

        [java] view plaincopyprint?

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

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

          我们在学生类里再加一个Address类。

          [java] view plaincopyprint?

          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. null;  
          38. try{  
          39. 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. new Address();  
          51. "杭州市");  
          52. new Student();  
          53. 123);  
          54.         stu1.setAddr(addr); 
          55.          
          56.         Student stu2 = (Student)stu1.clone(); 
          57.          
          58. "学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
          59. "学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
          60.     } 
          61. }

          打印结果:

          [plain] view plaincopyprint?

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

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

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

          [java] view plaincopyprint?

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

          打印结果:

          [plain] view plaincopyprint?


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

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

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

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

          [java] view plaincopyprint?

            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. null;  
            17. try{  
            18. 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. null;  
            50. try{  
            51. super.clone();   //浅复制   
            52. catch(CloneNotSupportedException e) {  
            53.             e.printStackTrace(); 
            54.         } 
            55. //深度复制   
            56. return stu;  
            57.     } 
            58. } 
            59. public class Test {  
            60.      
            61. public static void main(String args[]) {  
            62.          
            63. new Address();  
            64. "杭州市");  
            65. new Student();  
            66. 123);  
            67.         stu1.setAddr(addr); 
            68.          
            69.         Student stu2 = (Student)stu1.clone(); 
            70.          
            71. "学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
            72. "学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
            73.          
            74. "西湖区");  
            75.          
            76. "学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
            77. "学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
            78.     } 
            79. }

            打印结果:

            [plain] view plaincopyprint?

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

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

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

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

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

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

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

            java.util.Date:

            [java] view plaincopyprint?


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

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