一、淺克隆(ShallowClone)
在淺克隆中,如果原型對(duì)象的成員變量是值類(lèi)型,將復(fù)制一份給克隆對(duì)象;如果原型對(duì)象的成員變量是引用類(lèi)型,則將引用對(duì)象的地址復(fù)制一份給克隆對(duì)象,也就是說(shuō)原型對(duì)象和克隆對(duì)象的成員變量指向相同的內(nèi)存地址。
簡(jiǎn)單來(lái)說(shuō),在淺克隆中,當(dāng)對(duì)象被復(fù)制時(shí)只復(fù)制它本身和其中包含的值類(lèi)型的成員變量,而引用類(lèi)型的成員對(duì)象并沒(méi)有復(fù)制。
二、深克隆(DeepClone)
在深克隆中,無(wú)論原型對(duì)象的成員變量是值類(lèi)型還是引用類(lèi)型,都將復(fù)制一份給克隆對(duì)象,深克隆將原型對(duì)象的所有引用對(duì)象也復(fù)制一份給克隆對(duì)象。
簡(jiǎn)單來(lái)說(shuō),在深克隆中,除了對(duì)象本身被復(fù)制外,對(duì)象所包含的所有成員變量也將復(fù)制。
Java 中,如果需要實(shí)現(xiàn)深克隆,可以通過(guò)覆蓋 Object 類(lèi) 的 clone() 實(shí)現(xiàn),也可以通過(guò)序列化(Serialization)等方式來(lái)實(shí)現(xiàn)。
如果引用類(lèi)型里面還包含很多引用類(lèi)型,或者內(nèi)層引用類(lèi)型的類(lèi)里面又包含引用類(lèi)型,使用 clone 方法就會(huì)很麻煩。這時(shí)可以用序列化的方式來(lái)實(shí)現(xiàn)對(duì)象的深克隆。
序列化就是將對(duì)象寫(xiě)到流的過(guò)程,寫(xiě)到流中的對(duì)象是原有對(duì)象的一個(gè)拷貝,而原對(duì)象仍然存在于內(nèi)存中。通過(guò)序列化實(shí)現(xiàn)的拷貝不僅可以復(fù)制對(duì)象本身,而且可以復(fù)制其引用的成員對(duì)象,因此通過(guò)序列化將對(duì)象寫(xiě)到一個(gè)流中,再?gòu)牧骼飳⑵渥x出來(lái),可以實(shí)現(xiàn)深克隆。需要注意的是能夠?qū)崿F(xiàn)序列化的對(duì)象其類(lèi)必須實(shí)現(xiàn) Serializable 接口,否則無(wú)法實(shí)現(xiàn)序列化操作。
Java 提供的 Cloneable 接口和 Serializable 接口的代碼非常簡(jiǎn)單,它們都是空接口,這種空接口也稱(chēng)為標(biāo)識(shí)接口,標(biāo)識(shí)接口中沒(méi)有任何方法的定義,其作用是告訴 JRE 這些接口的實(shí)現(xiàn)類(lèi)是否具有某個(gè)功能,如是否支持克隆、是否支持序列化等。
三、解決多層克隆問(wèn)題
public class Outer implements Serializable{
private static final long serialVersionUID = 369285298572941L; //最好是顯式聲明ID
public Inner inner;
//Discription:[深度復(fù)制方法,需要對(duì)象及對(duì)象所有的對(duì)象屬性都實(shí)現(xiàn)序列化]
public Outer myclone() {
Outer outer = null;
try {
//將該對(duì)象序列化成流,因?yàn)閷?xiě)在流里的是對(duì)象的一個(gè)拷貝,
//而原對(duì)象仍然存在于JVM里面。所以利用這個(gè)特性可以實(shí)現(xiàn)對(duì)象的深拷貝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 將流序列化成對(duì)象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
}
Inner 也必須實(shí)現(xiàn) Serializable,否則無(wú)法序列化:
public class Inner implements Serializable{
private static final long serialVersionUID = 872390113109L; //最好是顯式聲明ID
public String name = "";
public Inner(String name) {
this.name = name;
}
@Override
public String toString() {
return "Inner的name值為:" + name;
}
}
這樣也能使兩個(gè)對(duì)象在內(nèi)存空間內(nèi)完全獨(dú)立存在,互不影響對(duì)方的值。
四、理解
假如說(shuō)想復(fù)制一個(gè)簡(jiǎn)單變量。很簡(jiǎn)單:
int apples = 5;
int pears = apples;
不僅僅是 int 類(lèi)型,其它七種基礎(chǔ)數(shù)據(jù)類(lèi)型(byte、short、char、long、float、double、boolean)同樣適用于該類(lèi)情況。但是如果復(fù)制的是一個(gè)對(duì)象,情況就有些復(fù)雜了:
class Student {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = stu1;
System.out.println("學(xué)生1:" + stu1.getNumber());
System.out.println("學(xué)生2:" + stu2.getNumber());
}
}
結(jié)果:
學(xué)生1:12345
學(xué)生2:12345
試著改變 stu2 實(shí)例的 number 字段,再打印結(jié)果看看:
stu2.setNumber(54321);
System.out.println("學(xué)生1:" + stu1.getNumber());
System.out.println("學(xué)生2:" + stu2.getNumber());
結(jié)果:
學(xué)生1:54321
學(xué)生2:54321
這就怪了,為什么改變學(xué)生 2 的學(xué)號(hào),學(xué)生 1 的學(xué)號(hào)也發(fā)生了變化呢?原因出在stu2 = stu1這一句。該句的作用是將 stu1 的引用賦值給 stu2,這樣 stu1 和 stu2 就指向內(nèi)存堆中同一個(gè)對(duì)象。如圖:

那么,怎樣才能復(fù)制一個(gè)對(duì)象呢?Object。它有 11 個(gè)方法,有兩個(gè) protected 的方法,其中一個(gè)為 clone 方法。Java 中所有的類(lèi)都是缺省的繼承 Object 類(lèi)的,查看源碼,發(fā)現(xiàn)有一個(gè) protected 的方法 clone():
/*
Creates and returns a copy of this object. The precise meaning of "copy" may
depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
它是一個(gè) native 方法,native 方法是非 Java 語(yǔ)言實(shí)現(xiàn)的代碼,供 Java 程序調(diào)用的,因?yàn)?Java 程序是運(yùn)行在 JVM 虛擬機(jī)上面的,要想訪(fǎng)問(wèn)到比較底層的與操作系統(tǒng)相關(guān)的就沒(méi)辦法了,只能由靠近操作系統(tǒng)的語(yǔ)言來(lái)實(shí)現(xiàn)。
- 第一次聲明,保證克隆對(duì)象將有單獨(dú)的內(nèi)存地址分配。
- 第二次表明,原始和克隆的對(duì)象應(yīng)該具有相同的類(lèi)類(lèi)型,但它不是強(qiáng)制性的。
- 第三次表明,原始和克隆的對(duì)象應(yīng)該是平等的 equals() 使用,但它不是強(qiáng)制性的。
因?yàn)槊總€(gè)類(lèi)直接或間接的父類(lèi)都是 Object,因此它們都含有 clone(),但是因?yàn)樵摲椒ㄊ?protected,所以都不能在類(lèi)外進(jìn)行訪(fǎng)問(wèn)。要想對(duì)一個(gè)對(duì)象進(jìn)行復(fù)制,就需要對(duì) clone 方法覆蓋。
五、為什么需要克隆對(duì)象?直接new一個(gè)對(duì)象不行嗎?
克隆的對(duì)象可能包含一些已經(jīng)修改過(guò)的屬性,而 new 出來(lái)的對(duì)象的屬性都還是初始化時(shí)候的值,所以當(dāng)需要一個(gè)新的對(duì)象來(lái)保存當(dāng)前對(duì)象的“狀態(tài)”就靠 clone 方法了。那么把這個(gè)對(duì)象的臨時(shí)屬性一個(gè)一個(gè)的賦值給新 new 的對(duì)象不也行嘛?可以,但是麻煩。況且源碼里 clone 是一個(gè) native 方法,極其方便。
Object a=new Object();
Object b;
b=a;
這種形式的代碼復(fù)制的是引用,即對(duì)象在內(nèi)存中的地址,a 和 b 對(duì)象仍然指向了同一個(gè)對(duì)象。而通過(guò) clone 方法賦值的對(duì)象跟原來(lái)的對(duì)象時(shí)同時(shí)獨(dú)立存在的。
六、如何實(shí)現(xiàn)克隆
Java中,數(shù)據(jù)類(lèi)型分為值類(lèi)型(基本數(shù)據(jù)類(lèi)型)和引用類(lèi)型。值類(lèi)型包括 byte、short、char、int、long、float、double、boolean 簡(jiǎn)單數(shù)據(jù)類(lèi)型;引用類(lèi)型包括類(lèi)、接口、數(shù)組等復(fù)雜類(lèi)型。淺克隆和深克隆的主要區(qū)別在于是否支持引用類(lèi)型的成員變量的復(fù)制,下面將對(duì)兩者進(jìn)行詳細(xì)介紹。
1??淺克隆
- 被復(fù)制的類(lèi)需要實(shí)現(xiàn) Clonenable 接口(不實(shí)現(xiàn)的話(huà)在調(diào)用 clone 方法會(huì)拋出 CloneNotSupportedException),該接口為標(biāo)記接口(不含任何方法)。
- 覆蓋 clone(),訪(fǎng)問(wèn)修飾符設(shè)為 public。方法中調(diào)用 super.clone() 得到需要復(fù)制的對(duì)象。(native 為本地方法)
下面對(duì)上面那個(gè)方法進(jìn)行改造:
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("學(xué)生1:" + stu1.getNumber());
System.out.println("學(xué)生2:" + stu2.getNumber());
stu2.setNumber(54321);
System.out.println("學(xué)生1:" + stu1.getNumber());
System.out.println("學(xué)生2:" + stu2.getNumber());
System.out.println(stu1 == stu2); // false
}
}
結(jié)果:
學(xué)生1:12345
學(xué)生2:12345
學(xué)生1:12345
學(xué)生2:54321
false -----說(shuō)明這兩個(gè)對(duì)象不是同一個(gè)對(duì)象
2??深克隆
在學(xué)生類(lèi)里再加一個(gè) Address 類(lèi)。
class Address {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
在 main 方法中試著改變 addr 實(shí)例的地址:
addr.setAdd("西湖區(qū)");
System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
學(xué)生1:123,地址:西湖區(qū)
學(xué)生2:123,地址:西湖區(qū)
原因是淺復(fù)制只是復(fù)制了 addr 變量的引用,并沒(méi)有真正的開(kāi)辟另一塊空間,將值復(fù)制后再將引用返回給新對(duì)象。所以,為了達(dá)到真正的復(fù)制對(duì)象,而不是純粹引用復(fù)制。需要將 Address 類(lèi)可復(fù)制化,并且修改 clone 方法,完整代碼如下:
class Address implements Cloneable {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); //淺復(fù)制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address)addr.clone(); //深度復(fù)制
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖區(qū)");
System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
學(xué)生1:123,地址:西湖區(qū)
學(xué)生2:123,地址:杭州市
最后可以看看 API 里其中一個(gè)實(shí)現(xiàn)了 clone 方法的類(lèi):java.util.Date:
/**
* Return a copy of this object.
*/
public Object clone() {
Date d = null;
try {
d = (Date)super.clone();
if (cdate != null) {
d.cdate = (BaseCalendar.Date) cdate.clone();
}
} catch (CloneNotSupportedException e) {} // Won't happen
return d;
}
該類(lèi)其實(shí)也屬于深度復(fù)制。
七、總結(jié)
實(shí)現(xiàn)對(duì)象克隆有兩種方式:
1??實(shí)現(xiàn) Cloneable 接口并重寫(xiě) Object 類(lèi)中的 clone()。
2??實(shí)現(xiàn) Serializable 接口,通過(guò)對(duì)象的序列化和反序列化實(shí)現(xiàn)克隆,可以實(shí)現(xiàn)真正的深度克隆。
3??注意:基于序列化和反序列化實(shí)現(xiàn)的克隆不僅僅是深度克隆,更重要的是通過(guò)泛型限定,可以檢查出要克隆的對(duì)象是否支持序列化,這項(xiàng)檢查是編譯器完成的,不是在運(yùn)行時(shí)拋出異常,這種是方案明顯優(yōu)于使用 Object 類(lèi)的 clone 方法克隆對(duì)象。讓問(wèn)題在編譯的時(shí)候暴露出來(lái)總是優(yōu)于把問(wèn)題留到運(yùn)行時(shí)。