Java提高篇——对象克隆
Java提高篇——對(duì)象克隆(復(fù)制)
閱讀目錄
- 為什么要克隆?
- 如何實(shí)現(xiàn)克隆
- 淺克隆和深克隆
- 解決多層克隆問(wèn)題
- 總結(jié)
假如說(shuō)你想復(fù)制一個(gè)簡(jiǎn)單變量。很簡(jiǎn)單:
int apples = 5; int pears = apples;不僅僅是int類型,其它七種原始數(shù)據(jù)類型(boolean,char,byte,short,float,double.long)同樣適用于該類情況。
但是如果你復(fù)制的是一個(gè)對(duì)象,情況就有些復(fù)雜了。
假設(shè)說(shuō)我是一個(gè)beginner,我會(huì)這樣寫:
class Student { private int number; </span><span style="margin:0px;padding:0px;line-height:1.8;color:rgb(0,0,255);">public</span> <span style="margin:0px;padding:0px;line-height:1.8;color:rgb(0,0,255);">int</span><span style="margin:0px;padding:0px;line-height:1.8;"> getNumber() { </span><span style="margin:0px;padding:0px;line-height:1.8;color:rgb(0,0,255);">return</span><span style="margin:0px;padding:0px;line-height:1.8;"> number; } </span><span style="margin:0px;padding:0px;line-height:1.8;color:rgb(0,0,255);">public</span> <span style="margin:0px;padding:0px;line-height:1.8;color:rgb(0,0,255);">void</span> setNumber(<span style="margin:0px;padding:0px;line-height:1.8;color:rgb(0,0,255);">int</span><span style="margin:0px;padding:0px;line-height:1.8;"> number) { </span><span style="margin:0px;padding:0px;line-height:1.8;color:rgb(0,0,255);">this</span>.number =<span style="margin:0px;padding:0px;line-height:1.8;"> number; }}
public class Test {
}
結(jié)果:
學(xué)生1:12345 ?
學(xué)生2:12345 ?
?
這里我們自定義了一個(gè)學(xué)生類,該類只有一個(gè)number字段。
我們新建了一個(gè)學(xué)生實(shí)例,然后將該值賦值給stu2實(shí)例。(Student stu2 = stu1;)
再看看打印結(jié)果,作為一個(gè)新手,拍了拍胸腹,對(duì)象復(fù)制不過(guò)如此,
難道真的是這樣嗎?
我們?cè)囍淖僺tu2實(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) 這一句。該語(yǔ)句的作用是將stu1的引用賦值給stu2,
這樣,stu1和stu2指向內(nèi)存堆中同一個(gè)對(duì)象。如圖:
那么,怎樣才能達(dá)到復(fù)制一個(gè)對(duì)象呢?
是否記得萬(wàn)類之王Object。它有11個(gè)方法,有兩個(gè)protected的方法,其中一個(gè)為clone方法。
在Java中所有的類都是缺省的繼承自Java語(yǔ)言包中的Object類的,查看它的源碼,你可以把你的JDK目錄下的src.zip復(fù)制到其他地方然后解壓,里面就是所有的源碼。發(fā)現(xiàn)里面有一個(gè)訪問(wèn)限定符為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:
x.clone() != x will be true
x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
仔細(xì)一看,它還是一個(gè)native方法,大家都知道native方法是非Java語(yǔ)言實(shí)現(xiàn)的代碼,供Java程序調(diào)用的,因?yàn)镴ava程序是運(yùn)行在JVM虛擬機(jī)上面的,要想訪問(wèn)到比較底層的與操作系統(tǒng)相關(guān)的就沒(méi)辦法了,只能由靠近操作系統(tǒng)的語(yǔ)言來(lái)實(shí)現(xiàn)。
因?yàn)槊總€(gè)類直接或間接的父類都是Object,因此它們都含有clone()方法,但是因?yàn)樵摲椒ㄊ莗rotected,所以都不能在類外進(jìn)行訪問(wèn)。
要想對(duì)一個(gè)對(duì)象進(jìn)行復(fù)制,就需要對(duì)clone方法覆蓋。
回到頂部為什么要克隆?
大家先思考一個(gè)問(wèn)題,為什么需要克隆對(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ì)象不也行嘛?可以是可以,但是一來(lái)麻煩不說(shuō),二來(lái),大家通過(guò)上面的源碼都發(fā)現(xiàn)了clone是一個(gè)native方法,就是快啊,在底層實(shí)現(xiàn)的。
提個(gè)醒,我們常見(jiàn)的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)克隆
先介紹一下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。
在Java語(yǔ)言中,數(shù)據(jù)類型分為值類型(基本數(shù)據(jù)類型)和引用類型,值類型包括int、double、byte、boolean、char等簡(jiǎn)單數(shù)據(jù)類型,引用類型包括類、接口、數(shù)組等復(fù)雜類型。淺克隆和深克隆的主要區(qū)別在于是否支持引用類型的成員變量的復(fù)制,下面將對(duì)兩者進(jìn)行詳細(xì)介紹。
一般步驟是(淺克隆):
1.?被復(fù)制的類需要實(shí)現(xiàn)Clonenable接口(不實(shí)現(xiàn)的話在調(diào)用clone方法會(huì)拋出CloneNotSupportedException異常), 該接口為標(biāo)記接口(不含任何方法)
2.?覆蓋clone()方法,訪問(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();
}
}
結(jié)果:
學(xué)生1:12345??
學(xué)生2:12345??
學(xué)生1:12345??
學(xué)生2:54321
如果你還不相信這兩個(gè)對(duì)象不是同一個(gè)對(duì)象,那么你可以看看這一句:
System.out.println(stu1 == stu2); // false上面的復(fù)制被稱為淺克隆。
還有一種稍微復(fù)雜的深度復(fù)制:
我們?cè)趯W(xué)生類里再加一個(gè)Address類。
1 class Address {2 private String add;
3
4 public String getAdd() {
5 return add;
6 }
7
8 public void setAdd(String add) {
9 this.add = add;
10 }
11
12 }
13
14 class Student implements Cloneable{
15 private int number;
16
17 private Address addr;
18
19 public Address getAddr() {
20 return addr;
21 }
22
23 public void setAddr(Address addr) {
24 this.addr = addr;
25 }
26
27 public int getNumber() {
28 return number;
29 }
30
31 public void setNumber(int number) {
32 this.number = number;
33 }
34
35 @Override
36 public Object clone() {
37 Student stu = null;
38 try{
39 stu = (Student)super.clone();
40 }catch(CloneNotSupportedException e) {
41 e.printStackTrace();
42 }
43 return stu;
44 }
45 }
46 public class Test {
47
48 public static void main(String args[]) {
49
50 Address addr = new Address();
51 addr.setAdd(“杭州市”);
52 Student stu1 = new Student();
53 stu1.setNumber(123);
54 stu1.setAddr(addr);
55
56 Student stu2 = (Student)stu1.clone();
57
58 System.out.println(“學(xué)生1:” + stu1.getNumber() + “,地址:” + stu1.getAddr().getAdd());
59 System.out.println(“學(xué)生2:” + stu2.getNumber() + “,地址:” + stu2.getAddr().getAdd());
60 }
61 }
結(jié)果:
學(xué)生1:123,地址:杭州市??
學(xué)生2:123,地址:杭州市 ?
?
乍一看沒(méi)什么問(wèn)題,真的是這樣嗎?
我們?cè)趍ain方法中試著改變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ū)
這就奇怪了,怎么兩個(gè)學(xué)生的地址都改變了?
原因是淺復(fù)制只是復(fù)制了addr變量的引用,并沒(méi)有真正的開(kāi)辟另一塊空間,將值復(fù)制后再將引用返回給新對(duì)象。
所以,為了達(dá)到真正的復(fù)制對(duì)象,而不是純粹引用復(fù)制。我們需要將Address類可復(fù)制化,并且修改clone方法,完整代碼如下:
1 package abc;2
3 class Address implements Cloneable {
4 private String add;
5
6 public String getAdd() {
7 return add;
8 }
9
10 public void setAdd(String add) {
11 this.add = add;
12 }
13
14 @Override
15 public Object clone() {
16 Address addr = null;
17 try{
18 addr = (Address)super.clone();
19 }catch(CloneNotSupportedException e) {
20 e.printStackTrace();
21 }
22 return addr;
23 }
24 }
25
26 class Student implements Cloneable{
27 private int number;
28
29 private Address addr;
30
31 public Address getAddr() {
32 return addr;
33 }
34
35 public void setAddr(Address addr) {
36 this.addr = addr;
37 }
38
39 public int getNumber() {
40 return number;
41 }
42
43 public void setNumber(int number) {
44 this.number = number;
45 }
46
47 @Override
48 public Object clone() {
49 Student stu = null;
50 try{
51 stu = (Student)super.clone(); //淺復(fù)制
52 }catch(CloneNotSupportedException e) {
53 e.printStackTrace();
54 }
55 stu.addr = (Address)addr.clone(); //深度復(fù)制
56 return stu;
57 }
58 }
59 public class Test {
60
61 public static void main(String args[]) {
62
63 Address addr = new Address();
64 addr.setAdd(“杭州市”);
65 Student stu1 = new Student();
66 stu1.setNumber(123);
67 stu1.setAddr(addr);
68
69 Student stu2 = (Student)stu1.clone();
70
71 System.out.println(“學(xué)生1:” + stu1.getNumber() + “,地址:” + stu1.getAddr().getAdd());
72 System.out.println(“學(xué)生2:” + stu2.getNumber() + “,地址:” + stu2.getAddr().getAdd());
73
74 addr.setAdd(“西湖區(qū)”);
75
76 System.out.println(“學(xué)生1:” + stu1.getNumber() + “,地址:” + stu1.getAddr().getAdd());
77 System.out.println(“學(xué)生2:” + stu2.getNumber() + “,地址:” + stu2.getAddr().getAdd());
78 }
79 }
結(jié)果:
學(xué)生1:123,地址:杭州市學(xué)生2:123,地址:杭州市
學(xué)生1:123,地址:西湖區(qū)
學(xué)生2:123,地址:杭州市
這樣結(jié)果就符合我們的想法了。
?
最后我們可以看看API里其中一個(gè)實(shí)現(xiàn)了clone方法的類:
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;
}該類其實(shí)也屬于深度復(fù)制。
參考文檔:Java如何復(fù)制對(duì)象
回到頂部淺克隆和深克隆
1、淺克隆
在淺克隆中,如果原型對(duì)象的成員變量是值類型,將復(fù)制一份給克隆對(duì)象;如果原型對(duì)象的成員變量是引用類型,則將引用對(duì)象的地址復(fù)制一份給克隆對(duì)象,也就是說(shuō)原型對(duì)象和克隆對(duì)象的成員變量指向相同的內(nèi)存地址。
簡(jiǎn)單來(lái)說(shuō),在淺克隆中,當(dāng)對(duì)象被復(fù)制時(shí)只復(fù)制它本身和其中包含的值類型的成員變量,而引用類型的成員對(duì)象并沒(méi)有復(fù)制。
在Java語(yǔ)言中,通過(guò)覆蓋Object類的clone()方法可以實(shí)現(xiàn)淺克隆。
2、深克隆
在深克隆中,無(wú)論原型對(duì)象的成員變量是值類型還是引用類型,都將復(fù)制一份給克隆對(duì)象,深克隆將原型對(duì)象的所有引用對(duì)象也復(fù)制一份給克隆對(duì)象。
簡(jiǎn)單來(lái)說(shuō),在深克隆中,除了對(duì)象本身被復(fù)制外,對(duì)象所包含的所有成員變量也將復(fù)制。
在Java語(yǔ)言中,如果需要實(shí)現(xiàn)深克隆,可以通過(guò)覆蓋Object類的clone()方法實(shí)現(xiàn),也可以通過(guò)序列化(Serialization)等方式來(lái)實(shí)現(xiàn)。
(如果引用類型里面還包含很多引用類型,或者內(nèi)層引用類型的類里面又包含引用類型,使用clone方法就會(huì)很麻煩。這時(shí)我們可以用序列化的方式來(lái)實(shí)現(xiàn)對(duì)象的深克隆。)
序列化就是將對(duì)象寫到流的過(guò)程,寫到流中的對(duì)象是原有對(duì)象的一個(gè)拷貝,而原對(duì)象仍然存在于內(nèi)存中。通過(guò)序列化實(shí)現(xiàn)的拷貝不僅可以復(fù)制對(duì)象本身,而且可以復(fù)制其引用的成員對(duì)象,因此通過(guò)序列化將對(duì)象寫到一個(gè)流中,再?gòu)牧骼飳⑵渥x出來(lái),可以實(shí)現(xiàn)深克隆。需要注意的是能夠?qū)崿F(xiàn)序列化的對(duì)象其類必須實(shí)現(xiàn)Serializable接口,否則無(wú)法實(shí)現(xiàn)序列化操作。
擴(kuò)展
回到頂部Java語(yǔ)言提供的Cloneable接口和Serializable接口的代碼非常簡(jiǎn)單,它們都是空接口,這種空接口也稱為標(biāo)識(shí)接口,標(biāo)識(shí)接口中沒(méi)有任何方法的定義,其作用是告訴JRE這些接口的實(shí)現(xiàn)類是否具有某個(gè)功能,如是否支持克隆、是否支持序列化等。 解決多層克隆問(wèn)題
如果引用類型里面還包含很多引用類型,或者內(nèi)層引用類型的類里面又包含引用類型,使用clone方法就會(huì)很麻煩。這時(shí)我們可以用序列化的方式來(lái)實(shí)現(xiàn)對(duì)象的深克隆。
1 public class Outer implements Serializable{
2 private static final long serialVersionUID = 369285298572941L; //最好是顯式聲明ID
3 public Inner inner;
4 //Discription:[深度復(fù)制方法,需要對(duì)象及對(duì)象所有的對(duì)象屬性都實(shí)現(xiàn)序列化]
5 public Outer myclone() {
6 Outer outer = null;
7 try {?// 將該對(duì)象序列化成流,因?yàn)閷懺诹骼锏氖菍?duì)象的一個(gè)拷貝,而原對(duì)象仍然存在于JVM里面。所以利用這個(gè)特性可以實(shí)現(xiàn)對(duì)象的深拷貝
8 ByteArrayOutputStream baos = new ByteArrayOutputStream();
9 ObjectOutputStream oos = new ObjectOutputStream(baos);
10 oos.writeObject(this);
11 // 將流序列化成對(duì)象
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也必須實(shí)現(xiàn)Serializable,否則無(wú)法序列化:
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 }這樣也能使兩個(gè)對(duì)象在內(nèi)存空間內(nèi)完全獨(dú)立存在,互不影響對(duì)方的值。
回到頂部總結(jié)
實(shí)現(xiàn)對(duì)象克隆有兩種方式:
??1). 實(shí)現(xiàn)Cloneable接口并重寫Object類中的clone()方法;
??2). 實(shí)現(xiàn)Serializable接口,通過(guò)對(duì)象的序列化和反序列化實(shí)現(xiàn)克隆,可以實(shí)現(xiàn)真正的深度克隆。
注意:基于序列化和反序列化實(shí)現(xiàn)的克隆不僅僅是深度克隆,更重要的是通過(guò)泛型限定,可以檢查出要克隆的對(duì)象是否支持序列化,這項(xiàng)檢查是編譯器完成的,不是在運(yùn)行時(shí)拋出異常,這種是方案明顯優(yōu)于使用Object類的clone方法克隆對(duì)象。讓問(wèn)題在編譯的時(shí)候暴露出來(lái)總是優(yōu)于把問(wèn)題留到運(yùn)行時(shí)。
文章來(lái)源:https://blog.csdn.net/m0_37601917/article/details/80741773
總結(jié)
以上是生活随笔為你收集整理的Java提高篇——对象克隆的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Hyperledger Fabric 通
- 下一篇: java创建access数据库_使用Ja