Java 簡述對象克隆(複製)

  • 假如說你想複製一個簡單變量。很簡單:

int apples = 5;  
int pears = apples;  

不僅僅是int類型,其它七種原始數據類型(boolean,char,byte,short,float,double.long)同樣適用於該類情況。

但是如果你複製的是一個對象,情況就有些複雜了。

假設說我是一個beginner,我會這樣寫:

public class Student {
int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}

public class OuterMyTest {
public static void main(String[] args) {
Student stu1 = new Student();
stu1.setNumber(12345);  
Student stu2= stu1;
System.out.println(stu1.getNumber());
System.out.println(stu2.getNumber());
stu2.setNumber(54321);
System.out.println(stu1.getNumber());
System.out.println(stu2.getNumber());

                System.out.println(stu1.equals(stu2));

}
}

運行結果:

12345
12345
54321
54321

true


這就怪了,爲什麼改變學生2的學號,學生1的學號也發生了變化呢?

原因出在(Studentstu2= stu1;) 這一句。該語句的作用是stu1的引用賦值給stu2

這樣,stu1和stu2指向內存堆中同一個對象。如圖:

那麼,怎樣才能達到複製一個對象呢?


是否記得萬類之王Object。

它有11個方法,有兩個protected的方法,其中一個爲clone方法。

在Java中所有的類都是缺省的繼承自Java語言包中的Object類的,查看它的源碼,你可以把你的JDK目錄下的src.zip複製到其他地方然後解壓,裏面就是所有的源碼。發現裏面有一個訪問限定符爲protected的方法clone():

protected native Object clone() throws CloneNotSupportedException;

仔細一看,它還是一個native方法,大家都知道native方法是非Java語言實現的代碼,供Java程序調用的,因爲Java程序是運行在JVM虛擬機上面的,要想訪問到比較底層的與操作系統相關的就沒辦法了,只能由靠近操作系統的語言來實現。

  1. 第一次聲明保證克隆對象將有單獨的內存地址分配。
  2. 第二次聲明表明,原始和克隆的對象應該具有相同的類類型,但它不是強制性的。
  3. 第三聲明表明,原始和克隆的對象應該是平等的equals()方法使用,但它不是強制性的。

因爲每個類直接或間接的父類都是Object,因此它們都含有clone()方法,但是因爲該方法是protected,所以都不能在類外進行訪問。

要想對一個對象進行復制,就需要對clone方法覆蓋。


問:爲什麼要克隆?

答案是:克隆的對象可能包含一些已經修改過的屬性,而new出來的對象的屬性都還是初始化時候的值,所以當需要一個新的對象來保存當前對象的“狀態”就靠clone方法了。clone是一個native方法,在底層實現的,快。我們常見的Object a=new Object();Object b;b=a;這種形式的代碼複製的是引用,即對象在內存中的地址,a和b對象仍然指向了同一個對象。而通過clone方法賦值的對象跟原來的對象時同時獨立存在的。

問:如何實現克隆?

先介紹一下兩種不同的克隆方法,淺克隆(ShallowClone)深克隆(DeepClone)

在Java語言中,數據類型分爲值類型(基本數據類型)和引用類型,

值類型包括int、double、byte、boolean、char等簡單數據類型,

引用類型包括類、接口、數組等複雜類型。

淺克隆和深克隆的主要區別在於是否支持引用類型的成員變量的複製,下面將對兩者進行詳細介紹。

一般步驟是(淺克隆):

1. 被複制的類需要實現Clonenable接口(不實現的話在調用clone方法會拋出CloneNotSupportedException異常), 該接口爲標記接口(不含任何方法)

2. 覆蓋clone()方法,訪問修飾符設爲public方法中調用super.clone()方法得到需要的複製對象。(native爲本地方法)

下面對上面那個方法進行改造:

public class Student implements Cloneable{
int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();  
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return stu;  
    }  

}


public class OuterMyTest {
public static void main(String[] args) {
Student stu1 = new Student();
stu1.setNumber(12345);  
//Student stu2 = stu1;
Student stu2 = (Student) stu1.clone();
System.out.println(stu1.getNumber());
System.out.println(stu2.getNumber());
stu2.setNumber(54321);
System.out.println(stu1.getNumber());
System.out.println(stu2.getNumber());

               System.out.println(stu1.equals(stu2));

}
}


運行結果:

12345
12345
12345
54321

false



還有一種稍微複雜的深度複製:

我們在學生類裏再加一個Address類。

public class Address {
String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}

public class Student implements Cloneable {
int number;
private Address addr;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Object clone() {
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
}
public class OuterMyTest {


public static void main(String[] args) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(12345);  
stu1.setAddr(addr);

Student stu2 = (Student) stu1.clone();
System.out.println(stu1.getNumber() + " " + stu1.getAddr().getAdd());
System.out.println(stu2.getNumber() + " " + stu2.getAddr().getAdd());

stu2.setNumber(54321);
addr.setAdd("衢州市");  
System.out.println(stu1.getNumber() + " " + stu1.getAddr().getAdd());
System.out.println(stu2.getNumber() + " " + stu2.getAddr().getAdd());
System.out.println(stu1.equals(stu2));

}

}


運行結果:

12345 杭州市
12345 杭州市
12345 衢州市
54321 衢州市
false


這就奇怪了,怎麼兩個學生的地址都改變了?

原因是淺複製只是複製了addr變量的引用,並沒有真正的開闢另一塊空間,將值複製後再將引用返回給新對象。

所以,爲了達到真正的複製對象,而不是純粹引用複製。我們需要將Address類可複製化,並且修改clone方法

public class Address implements Cloneable{

String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
public Object clone() {
Address add = null;
try {
add = (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return add;
}
}


public class Student implements Cloneable {
int number;
private Address addr;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Object clone() {
Student stu = null;
try {
stu = (Student) super.clone();   ////淺複製  
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address) addr.clone();   //深度複製  
return stu;
}
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
}



public class OuterMyTest {


public static void main(String[] args) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(12345);  
stu1.setAddr(addr);

Student stu2 = (Student) stu1.clone();
System.out.println(stu1.getNumber() + " " + stu1.getAddr().getAdd());
System.out.println(stu2.getNumber() + " " + stu2.getAddr().getAdd());

stu2.setNumber(54321);

addr.setAdd("衢州市");  
//stu2.getAddr().setAdd("衢州市");
System.out.println(stu1.getNumber() + " " + stu1.getAddr().getAdd());
System.out.println(stu2.getNumber() + " " + stu2.getAddr().getAdd());
System.out.println(stu1.equals(stu2));

}


}


運行結果:

12345 杭州市
12345 杭州市
12345 衢州市
54321 杭州市
false




淺克隆和深克隆

1、淺克隆

在淺克隆中,如果原型對象的成員變量是值類型,將複製一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址複製一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內存地址。

簡單來說,在淺克隆中,當對象被複制時只複製它本身和其中包含的值類型的成員變量,而引用類型的成員對象並沒有複製。

在Java語言中,通過覆蓋Object類的clone()方法可以實現淺克隆

2、深克隆

在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將複製一份給克隆對象,深克隆將原型對象的所有引用對象也複製一份給克隆對象。

簡單來說,在深克隆中,除了對象本身被複制外,對象所包含的所有成員變量也將複製。

在Java語言中,如果需要實現深克隆,可以通過覆蓋Object類的clone()方法實現,也可以通過序列化(Serialization)等方式來實現。

如果引用類型裏面還包含很多引用類型,或者內層引用類型的類裏面又包含引用類型,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實現對象的深克隆。


序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在於內存中。通過序列化實現的拷貝不僅可以複製對象本身,而且可以複製其引用的成員對象,因此通過序列化將對象寫到一個流中,再從流裏將其讀出來,可以實現深克隆。需要注意的是能夠實現序列化的對象其類必須實現Serializable接口,否則無法實現序列化操作。

解決多層克隆問題

如果引用類型裏面還包含很多引用類型,或者內層引用類型的類裏面又包含引用類型,使用clone方法就會很麻煩。

這時我們可以用序列化的方式來實現對象的深克隆。

複製代碼
 1 public class Outer implements Serializable{
 2   private static final long serialVersionUID = 369285298572941L;  //最好是顯式聲明ID
 3   public Inner inner;
 4  //Discription:[深度複製方法,需要對象及對象所有的對象屬性都實現序列化] 
 5   public Outer myclone() {
 6       Outer outer = null;
 7       try { // 將該對象序列化成流,因爲寫在流裏的是對象的一個拷貝,而原對象仍然存在於JVM裏面。所以利用這個特性可以實現對象的深拷貝
 8           ByteArrayOutputStream baos = new ByteArrayOutputStream();
 9           ObjectOutputStream oos = new ObjectOutputStream(baos);
10           oos.writeObject(this);
11       // 將流序列化成對象
12           ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
13           ObjectInputStream ois = new ObjectInputStream(bais);
14           outer = (Outer) ois.readObject();
15       } catch (IOException e) {
16           e.printStackTrace();
17       } catch (ClassNotFoundException e) {
18           e.printStackTrace();
19       }
20       return outer;
21   }
22 }
複製代碼

Inner也必須實現Serializable,否則無法序列化:

複製代碼
 1 public class Inner implements Serializable{
 2   private static final long serialVersionUID = 872390113109L; //最好是顯式聲明ID
 3   public String name = "";
 4 
 5   public Inner(String name) {
 6       this.name = name;
 7   }
 8 
 9   @Override
10   public String toString() {
11       return "Inner的name值爲:" + name;
12   }
13 }
複製代碼

這樣也能使兩個對象在內存空間內完全獨立存在,互不影響對方的值。



Java語言提供的Cloneable接口和Serializable接口的代碼非常簡單,它們都是空接口,這種空接口也稱爲標識接口,標識接口中沒有任何方法的定義,其作用是告訴JRE這些接口的實現類是否具有某個功能,如是否支持克隆、是否支持序列化等。


總結:

實現對象克隆有兩種方式:

  1). 實現Cloneable接口並重寫Object類中的clone()方法;

  2). 實現Serializable接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆。

注意:基於序列化和反序列化實現的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來總是優於把問題留到運行時。



轉載自:http://www.cnblogs.com/Qian123/p/5710533.html

發佈了43 篇原創文章 · 獲贊 29 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章