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

前言

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

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

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

浅拷贝

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

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

如何进行浅拷贝

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

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

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

深拷贝

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

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

如何进行深拷贝

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

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

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

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

代码示例 - 直接赋值

  测试类:

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

  测试代码:

 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     
 6      // 实现clone浅拷贝方法
 7     public A clone() throws CloneNotSupportedException
 8     {
 9         return (A)super.clone();
10     }
11     // 打印对象内的整型变量值
12     public void showValueInt() {
13         System.out.println(valueInt);
14     }
15     // 打印对象内的字符串变量值
16     public void showValueStr() {
17         System.out.println(valueStr);
18     }
19     // 设置对象内的整型变量值
20     public boolean setValue (int valueInt) {
21         this.valueInt = valueInt;
22         return true;
23     }
24     // 设置对象内的字符串变量值
25     public boolean setValue (String valueStr) {
26         this.valueStr = valueStr;
27         return true;
28     }
29     
30     // 值1
31     private int valueInt;
32     // 值2
33     private 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     
 6     // 实现clone深拷贝方法
 7     public 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     public void showValueInt() {
17         System.out.println(valueInt);
18     }
19     // 打印对象内的字符串变量值
20     public void showValueStr() {
21         System.out.println(valueStr);
22     }
23     // 设置对象内的整型变量值
24     public boolean setValue (int valueInt) {
25         this.valueInt = valueInt;
26         return true;
27     }
28     // 设置对象内的字符串变量值
29     public boolean setValue (String valueStr) {
30         this.valueStr = valueStr;
31         return true;
32     }
33     
34     // 值1
35     private int valueInt;
36     // 值2
37     private 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的方式确实有点 "笨重",但必须严格的遵守才能写出高质量的代码。

 

posted on 2014-12-08 10:29  空山悟  阅读(196)  评论(0编辑  收藏  举报

导航