首页 > 代码库 > 对象复制 - 浅拷贝与深拷贝

对象复制 - 浅拷贝与深拷贝

前言

  在前面的文章中,提到过Java之间如果发生了对象赋值,那么其意义是赋值的两个对象都指向同一片内存区域。

  那么,如果我希望得到的是一份新的副本 - 即可以随意更改而不影响原始对象呢?

  那就涉及到本文要探讨的话题 - 对象的浅拷贝与深拷贝。

浅拷贝

  若对象之间发生浅拷贝,那么首先肯定的是会创建一个新的对象副本(这就不同与对象间的直接赋值).

  然后所有域进行简单的直接复制 - 非对象域直接拷贝过来,对象域则拷贝此对象的地址(因为对象在java中的本质就是一个 "指针")

如何进行浅拷贝

  1. 为目标类继承Cloneable接口。

  2. 使用public访问修辞符重新定义clone方法且抛出CloneNotSupportedException异常。

  3. 在clone方法中调用父类的clone方法并将结果强制转型为当前类类型后返回。

深拷贝

  看完了浅拷贝,你很有可能对其处理对象域的操作不满意。因为对象如果直接拷贝地址过来,则副本中该对象域的改变势必也会影响到原始对象中的对象域。

  相对于浅拷贝,深拷贝是指用户自定义对象域的拷贝方式。

如何进行深拷贝

  1. 为目标类继承Cloneable接口。

  2. 使用public访问修辞符重新定义clone方法且抛出CloneNotSupportedException异常。

  3. 在clone方法中调用父类的clone方法并将结果强制转型为当前类类型。

  4. 对3中处理得到的结果以自定义的方式拷贝对象域部分。

代码示例 - 直接赋值

  测试类:

package test;// 测试类Apublic class A {    public        // 打印对象内的整型变量值        void showValueInt() {            System.out.println(valueInt);        }        // 打印对象内的字符串变量值        void showValueStr() {            System.out.println(valueStr);        }        // 设置对象内的整型变量值        boolean setValue (int valueInt) {            this.valueInt = valueInt;            return true;        }        // 设置对象内的字符串变量值        boolean setValue (String valueStr) {            this.valueStr = valueStr;            return true;        }    private        // 值1        int valueInt;        // 值2        String valueStr;}  

  测试代码:

 1 package test; 2  3 public class Java7Learn { 4      5     public static void main(String[] args){ 6          7         // 创建一个对象a1,并让它直接赋值给a2。 8         A a1 = new A(); 9         A a2 = a1;10         11         // 设置a1的两个变量值并打印出来12         a1.setValue(1);13         a1.setValue("this is a1");14         a1.showValueInt();15         a1.showValueStr();16         17         // 设置a2的两个变量值并打印出来18         a2.setValue(2);19         a2.setValue("this is a2");20         a2.showValueInt();21         a2.showValueStr();22         23         // 显示结果发现 - a1的对象值都变成a2的了。24         a1.showValueInt();25         a1.showValueStr();26     }    27 }

  运行结果

  

代码示例 - 浅拷贝

  首先按照前文所讲的浅拷贝的方法修改测试类:

 1 package test; 2  3 // 测试类A 4 public class A implements Cloneable { 5     public 6         // 实现clone浅拷贝方法 7         A clone() throws CloneNotSupportedException 8         { 9             return (A)super.clone();10         }11         // 打印对象内的整型变量值12         void showValueInt() {13             System.out.println(valueInt);14         }15         // 打印对象内的字符串变量值16         void showValueStr() {17             System.out.println(valueStr);18         }19         // 设置对象内的整型变量值20         boolean setValue (int valueInt) {21             this.valueInt = valueInt;22             return true;23         }24         // 设置对象内的字符串变量值25         boolean setValue (String valueStr) {26             this.valueStr = valueStr;27             return true;28         }29     private30         // 值131         int valueInt;32         // 值233         String valueStr;34 }

  测试代码:

package test;public class Java7Learn {        public static void main(String[] args) throws CloneNotSupportedException{                // 创建一个对象a1,并将它克隆到a2。        A a1 = new A();        A a2 = a1.clone();                // 设置a1的两个变量值并打印出来        a1.setValue(1);        a1.setValue("this is a1");        a1.showValueInt();        a1.showValueStr();                // 设置a2的两个变量值并打印出来        a2.setValue(2);        a2.setValue("this is a2");        a2.showValueInt();        a2.showValueStr();                // 显示结果发现 - a1的整型对象值没变,但字符串型的变了。        a1.showValueInt();        a1.showValueStr();    }    }

  运行结果

  

  发现,a1的整型对象值没变,但字符串型的变了。这是和预期相一致的。

代码示例 - 深拷贝

  首先按照前文所讲的深拷贝的方法修改测试类:

 1 package test; 2  3 // 测试类A 4 public class A implements Cloneable { 5     public 6         // 实现clone深拷贝方法 7         A clone() throws CloneNotSupportedException 8         { 9             A cloned = (A)super.clone();10             // 自定义拷贝方式。11             // 一般定义的方式都是调用成员自己的clone函数。但测试的字符串默认赋值会深拷贝,就没有在这里继续clone了。12             cloned.valueStr = valueStr;13             return cloned;14         }15         // 打印对象内的整型变量值16         void showValueInt() {17             System.out.println(valueInt);18         }19         // 打印对象内的字符串变量值20         void showValueStr() {21             System.out.println(valueStr);22         }23         // 设置对象内的整型变量值24         boolean setValue (int valueInt) {25             this.valueInt = valueInt;26             return true;27         }28         // 设置对象内的字符串变量值29         boolean setValue (String valueStr) {30             this.valueStr = valueStr;31             return true;32         }33     private34         // 值135         int valueInt;36         // 值237         String valueStr;38 }

  测试代码:

 1 package test; 2  3 public class Java7Learn { 4      5     public static void main(String[] args) throws CloneNotSupportedException{ 6          7         // 创建一个对象a1,并将它克隆到a2。 8         A a1 = new A(); 9         A a2 = a1.clone();10         11         // 设置a1的两个变量值并打印出来12         a1.setValue(1);13         a1.setValue("this is a1");14         a1.showValueInt();15         a1.showValueStr();16         17         // 设置a2的两个变量值并打印出来18         a2.setValue(2);19         a2.setValue("this is a2");20         a2.showValueInt();21         a2.showValueStr();22         23         // 显示结果发现 - a1的所有对象值都没被影响。24         a1.showValueInt();25         a1.showValueStr();26     }    27 }

  运行结果

  

  a2所做的操作完全没有影响a1的域。

小结

  1. 要清晰区分对象直接赋值,浅拷贝,深拷贝这三者的底层机制。

  2. clone的方式确实有点 "笨重",但必须严格的遵守才能写出高质量的代码。

 

对象复制 - 浅拷贝与深拷贝