日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java进阶之对象克隆(复制)

發(fā)布時(shí)間:2023/12/3 java 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java进阶之对象克隆(复制) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)載自? ?Java進(jìn)階之對(duì)象克隆(復(fù)制)

假如說(shuō)你想復(fù)制一個(gè)簡(jiǎn)單變量。很簡(jiǎn)單:

int?apples =?5; ? int?pears = apples;

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

但是如果你復(fù)制的是一個(gè)對(duì)象,情況就有些復(fù)雜了。

假設(shè)說(shuō)我是一個(gè)beginner,我會(huì)這樣寫(xiě):

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

這里我們自定義了一個(gè)學(xué)生類(lèi),該類(lèi)只有一個(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)類(lèi)之王Object。它有11個(gè)方法,有兩個(gè)protected的方法,其中一個(gè)為clone方法。

在Java中所有的類(lèi)都是缺省的繼承自Java語(yǔ)言包中的Object類(lèi)的,查看它的源碼,你可以把你的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: 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;

仔細(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)。

  • 第一次聲明保證克隆對(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)樵摲椒ㄊ莗rotected,所以都不能在類(lèi)外進(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ù)類(lèi)型分為值類(lèi)型(基本數(shù)據(jù)類(lèi)型)和引用類(lèi)型,值類(lèi)型包括int、double、byte、boolean、char等簡(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)記接口(不含任何方法)

    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(); ?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()); ?} ? }

    結(jié)果:

    學(xué)生1:12345??
    學(xué)生2:12345??
    學(xué)生1:12345??
    學(xué)生2:54321

    ?

    如果你還不相信這兩個(gè)對(duì)象不是同一個(gè)對(duì)象,那么你可以看看這一句:

    System.out.println(stu1?==?stu2);?//?false

    上面的復(fù)制被稱(chēng)為淺克隆。

    還有一種稍微復(fù)雜的深度復(fù)制:

    我們?cè)趯W(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,地址:杭州市

    ?

    乍一看沒(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類(lèi)可復(fù)制化,并且修改clone方法,完整代碼如下:

    package abc; ?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,地址:杭州市

    ?

    這樣結(jié)果就符合我們的想法了。

    最后我們可以看看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ù)制。

    淺克隆和深克隆

    1、淺克隆

    在淺克隆中,如果原型對(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ù)制。

    ?

    在Java語(yǔ)言中,通過(guò)覆蓋Object類(lèi)的clone()方法可以實(shí)現(xiàn)淺克隆。

    2、深克隆

    在深克隆中,無(wú)論原型對(duì)象的成員變量是值類(lèi)型還是引用類(lèi)型,都將復(fù)制一份給克隆對(duì)象,深克隆將原型對(duì)象的所有引用對(duì)象也復(fù)制一份給克隆對(duì)象。

    簡(jiǎn)單來(lái)說(shuō),在深克隆中,除了對(duì)象本身被復(fù)制外,對(duì)象所包含的所有成員變量也將復(fù)制。

    ?

    在Java語(yǔ)言中,如果需要實(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)序列化操作。

    解決多層克隆問(wèn)題

    如果引用類(lèi)型里面還包含很多引用類(lèi)型,或者內(nèi)層引用類(lèi)型的類(lèi)里面又包含引用類(lèi)型,使用clone方法就會(huì)很麻煩。這時(shí)我們可以用序列化的方式來(lái)實(shí)現(xiàn)對(duì)象的深克隆。

    public?class?Outer?implements?Serializable{private?static?final?long?serialVersionUID =?369285298572941L; ?//最好是顯式聲明IDpublic?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;?//最好是顯式聲明IDpublic?String name =?"";public?Inner(String name)?{this.name = name;}@Overridepublic?String?toString()?{return?"Inner的name值為:"?+ name;} }

    這樣也能使兩個(gè)對(duì)象在內(nèi)存空間內(nèi)完全獨(dú)立存在,互不影響對(duì)方的值。

    總結(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)真正的深度克隆。

    Java語(yǔ)言提供的Cloneable接口和Serializable接口的代碼非常簡(jiǎn)單,它們都是空接口,這種空接口也稱(chēng)為標(biāo)識(shí)接口,標(biāo)識(shí)接口中沒(méi)有任何方法的定義,其作用是告訴JRE這些接口的實(shí)現(xiàn)類(lèi)是否具有某個(gè)功能,如是否支持克隆、是否支持序列化等。

    注意:基于序列化和反序列化實(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í)。

    創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

    總結(jié)

    以上是生活随笔為你收集整理的Java进阶之对象克隆(复制)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。