熟悉C++的朋友對這個話題應該很熟悉,淺拷貝就是指兩個對象共同擁有同一個值,一個對象改變了該值,也會影響到另一個對象。深拷貝就是兩個對象的值相等,但是互相獨立。本來想把以前寫的一篇文章擴充一下,沒想到居然牽扯出很多複雜的問題。本文測試環境是windows xp sp3中文版、NetBeans6.7.1,JDK1.6-update16。這裏拋磚引玉,希望大家能提寶貴意見。
首先,Java中常用的拷貝操作有三個,operator = 、拷貝構造函數 和 clone()方法。由於Java不支持運算符重載,我們無法在自己的自定義類型中定義operator=。拷貝構造函數大家應該很熟悉,現在看一下如何支持clone方法:
-
實現 Cloneable接口,因爲 Object的 clone方法將檢查類是否實現了 Cloneable接口,如果沒有將拋出異常 CloneNotSupportedException對象。 Cloneable接口沒有任何方法,只是個標誌,所以只需要簡單的寫上 implements Cloneable即可。
-
改寫從 Object繼承而來的 clone方法,使它的訪問權限爲 public,因爲爲了防止意外的支持 clone操作, Object的 clone方法是 protected權限。
通過上面的分析,可以看出,如果我們要給自己的類添加拷貝功能,我們可以添加拷貝構造函數和實現Cloneable接口。
現在,來看一下不同的類型在拷貝過程中的表現:
Operator = |
拷貝構造函數 |
clone方法 |
||
預定義非集合類型 |
深拷貝 |
如果支持拷貝構造函數的類型,則是深拷貝 |
不支持 |
|
自定義類型 |
淺拷貝 |
取決於實現 |
取決於實現 |
|
預定義集合類型 |
淺拷貝 |
會逐個調用每個元素的operator=方法 |
|
下面是測試代碼,首先測試的是預定義非集合類型的operator =操作:
- int x=1;
- int y=x;
- y=2;
- if(x!=y){
- System.out.println("deep copy");
- }
- Integer a=1;
- Integer b=a;
- b=2;
- if(!a.equals(b)){
- System.out.println("deep copy");
- }
- String m="ok";
- String n=m;
- n="no";
- if(!m.equals(n)){
- System.out.println("deep copy");
- }
程序運行後,輸出三行deep copy,測試結果表明,這三種類型的operator =操作都是深拷貝。由於我沒有測試完所有的預定義非集合類型,我這裏推測它們的operator =都是深拷貝。
下面測試預定義非集合類型的拷貝構造函數:
- Integer a=1;
- Integer b=new Integer(a);
- b=2;
- if(!a.equals(b)){
- System.out.println("deep copy");
- }
- String m="ok";
- String n=new String(m);
- n="no";
- if(!m.equals(n)){
- System.out.println("deep copy");
- }
程序運行後,輸出兩行deep copy,測試結果表明,這兩種類型的拷貝構造函數都是深拷貝。int沒有拷貝構造函數。
現在我們來測試自定義類型的operator=操作,假設我有一個類Person,代碼如下:
- public class Person implements Cloneable{
- private int age;
- private String name;
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getName() {
- return name;
- }
- void setName(String name) {
- this.name = name;
- }
- }
測試代碼:
- Person p=new Person();
- p.setAge(32);
- p.setName("陳抒");
- Person p2=p;
- p.setAge(33);
- p.setName("老陳");
- if( (p.getAge()!=p2.getAge())&&(!p.getName().equals(p2.getName())) ){
- System.out.println("deep copy");
- }
運行後,沒有輸出deep copy,說明這是淺拷貝。這裏就是我們常說的兩個引用之間的賦值,僅僅是讓兩個引用指向同一個對象。
現在,我們來測試預定義集合類型的operator=操作:
- ArrayList list1=new ArrayList();
- list1.add("yangzhou");
- ArrayList list2=list1;
- list1.clear();
- if(list2.isEmpty()){
- System.out.println("shallow copy");
- }
結果輸出爲shallow copy。
現在我來測試拷貝構造函數:
- ArrayList list1=new ArrayList();
- list1.add("yangzhou");
- ArrayList list2=new ArrayList(list1);
- list1.clear();
- if(list2.isEmpty()){
- System.out.println("shallow copy");
- }else{
- System.out.println("deep copy");
- }
輸出結果是deep copy;
clone方法的測試代碼只是將第三行換成list1.clone(),加上類型轉換,這裏不再貼代碼了。結果也證明是深拷貝 。
預定義集合類的深拷貝 實際上就是調用每個元素的operator =。如果元素都是自定義類型的化,實際上還是淺拷貝。現在來看測試代碼:
- ArrayList list1=new ArrayList();
- Person p1=new Person();
- p1.setAge(32);
- p1.setName("陳抒");
- list1.add(p1);
- ArrayList list2=(ArrayList) list1.clone();
- list2.get(0).setName("chenshu");
- if(list2.get(0).getName().equals(list1.get(0).getName())){
- System.out.println("shallow copy");
- }else{
- System.out.println("deep copy");
- }
輸出爲shallow copy,Person是自定義類型,它的operator =運算符只是引用之間賦值,是淺拷貝。因此當修改了list2的第一個元素指向的Person對象的name屬性,也就是修改了list1第一個元素所指向的Person對象的name屬性。對於這種拷貝,我自己起了一個名字,叫做第一層深拷貝。
現在我們有了表格中的結論,自己實現拷貝構造函數或者clone方法的時候就心裏有數多了。
假如我的自定義類型內部成員變量都是預定義非集合類型,那麼在clone方法中只需要調用Object.clone即可完成深拷貝操作。在拷貝構造函數中需要使用operator=來一個個的深拷貝;
假如我們的自定義類型內部成員變量有一些預定義類型,另一些是自定義類型,如果要深拷貝的話,最好調用自定義類型成員變量的拷貝構造函數或者clone方法。下面是例子代碼:
- public class Company {
- public Company(){
- }
- public Company(Company c){
- name=c.name;
- person=new Person(c.person);
- }
- private String name;
- private Person person;
- public Person getPerson() {
- return person;
- }
- public void setPerson(Person person) {
- this.person = person;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public Object clone() throws CloneNotSupportedException{
- Company c=new Company();
- c.setName(name);
- c.setPerson((Person) person.clone());
- return c;
- }
- }
- public class Person implements Cloneable{
- public Person(){
- }
- public Person(Person p){
- age=p.age;
- name=p.name;
- }
- private int age;
- private String name;
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public Object clone() throws CloneNotSupportedException{
- return super.clone();
- }
- }
Person類的兩個成員變量都是預定義非集合類型,所以只需要在clone方法中簡單的調用super.clone()即可實現深拷貝。Company類有一個Person成員變量,因此要調用Person的clone方法