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

歡迎訪問 生活随笔!

生活随笔

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

java

Java设计模式百例(番外) - Java的clone

發(fā)布時(shí)間:2025/3/20 java 68 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java设计模式百例(番外) - Java的clone 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文源碼見:https://github.com/get-set/get-designpatterns/tree/master/prototype

本文是為下一篇“Java設(shè)計(jì)模式百例 - 原型模式”做鋪墊,討論一下Java中的對(duì)象克隆。本文內(nèi)容綜合了《Effective Java》、《Java與模式》以及其他網(wǎng)上相關(guān)資料,希望能夠?qū)δ灿兴鶐椭?/p>

Java中,對(duì)象的創(chuàng)建除了用new關(guān)鍵字,還可以使用既有對(duì)象的clone()方法來復(fù)制自身達(dá)到創(chuàng)建一個(gè)新對(duì)象的目的。

關(guān)于對(duì)象克隆,Java中有通用約定:

通用約定1: x.clone() != x 必須為真。

對(duì)象克隆與引用的復(fù)制是有本質(zhì)區(qū)別的,區(qū)別就在于x.clone()后產(chǎn)生的對(duì)象與x并不位于同一塊內(nèi)存上,兩者是獨(dú)立的,修改兩者任何一方的成員都不會(huì)導(dǎo)致另一方發(fā)生變化。就像克隆羊多利(Dolly)不會(huì)因?yàn)槠洹盎蚰赣H”(很遺憾,它沒有名字,我們暫且諧音基因,就叫Jane吧)受傷或死亡而受傷或死亡。代碼舉例:

Sheep.java

public?class?Sheep?implements?Cloneable?{private?String?name;????//名字private?int?age;????????//年齡private?String?breed;???//品種private?EarTag?earTag;??//耳牌//?構(gòu)造方法public?Sheep(String?name,?int?age,?String?breed,?EarTag?earTag)?{this.name?=?name;this.age?=?age;this.breed?=?breed;this.earTag?=?earTag;}//?getters?&?setters@Overridepublic?Sheep?clone()?throws?CloneNotSupportedException?{return?(Sheep)?super.clone();}@Overridepublic?String?toString()?{return?this.name?+?"是一只"?+?this.age?+?"歲的"?+?this.breed?+?",?它的"?+?this.earTag.getColor()?+?"色耳牌上寫著"?+?this.earTag.getId()?+?"號(hào)。";} }

每只羊身上有個(gè)耳牌:

EarTag.java

public?class?EarTag?implements?Cloneable?{private?int?id;?????????//耳牌編號(hào)private?String?color;???//耳牌顏色//?構(gòu)造方法public?EarTag(int?id,?String?color)?{this.id?=?id;this.color?=?color;}//?getters?&?setters }

注意,

  • 以上兩個(gè)類均需要實(shí)現(xiàn)Cloneable接口,否則執(zhí)行clone()方法會(huì)報(bào)CloneNotSupportedException異常。

  • 若某個(gè)類允許其對(duì)象可以克隆,那么需要重寫clone()方法,并且聲明為public的,因?yàn)镺bject的clone()方法是protected,無法被非子類和不在當(dāng)前包的其他類或?qū)ο笳{(diào)用。

  • 派生類的clone()方法中,要調(diào)用super.clone(),以便能夠最終調(diào)用到Object.clone(),后者是個(gè)native方法,效率更高。

  • 克隆過程如下:

    Sheep?jane?=?new?Sheep("簡(jiǎn)",?5,?"多塞特白面綿羊",?new?EarTag(12345,?"×××")); System.out.println(jane); Sheep?dolly?=?jane.clone(); System.out.println("克隆后..."); dolly.setName("多利"); dolly.getEarTag().setId(12346); System.out.println(dolly);

    輸出結(jié)果為:

    簡(jiǎn)是一只5歲的多塞特白面綿羊,?它的×××色耳牌上寫著12345號(hào)。 克隆后... 多利是一只5歲的多塞特白面綿羊,?它的×××色耳牌上寫著12346號(hào)。

    仿佛很完美,所有的信息都克隆過來了,但是,我們?cè)诳匆幌耲ane這個(gè)對(duì)象(最后增加兩個(gè)輸出):

    System.out.println(jane); System.out.println(jane.getEarTag()?==?dolly.getEarTag());

    輸出結(jié)果為:

    簡(jiǎn)是一只5歲的多塞特白面綿羊,?它的×××色耳牌上寫著12346號(hào)。 true

    這就不對(duì)了,簡(jiǎn)的耳牌號(hào)也變了,而且我們看到兩只羊的耳牌是”==“的,也就是jane.earTag和dolly.earTag指向的是同一個(gè)對(duì)象。這在現(xiàn)實(shí)中是毫無道理的。可見,earTag這個(gè)成員變量是引用復(fù)制。

    淺復(fù)制

    上邊例子中,最終調(diào)用到的Object.clone()就是淺復(fù)制。所謂淺復(fù)制,可以理解為只復(fù)制成員變量的”值“。

  • 對(duì)于原生類型,其”值“就是實(shí)實(shí)在在的值,比如int age,是直接復(fù)制的;

  • 對(duì)于引用類型,其”值“就是引用本身,比如EarTag earTag,引用原來指向的是”×××編號(hào)為12345的牌子“,引用復(fù)制過來仍然是指向同樣的牌子,所以只是復(fù)制的值,而并未復(fù)制引用指向的對(duì)象;

  • (補(bǔ)充)對(duì)于引用類型,如果引用本身指向的是不可變類,比如String、Integer等,引用指向的對(duì)象內(nèi)容是不可變的,一旦需要改變,其實(shí)就是從新new了一個(gè)對(duì)象,因此可以認(rèn)為復(fù)制了引用指向的對(duì)象。其效果”看起來“和原生類型的待遇是一樣的。

  • 總結(jié)來說,被復(fù)制對(duì)象的所有原生類型變量和不可變類的引用都復(fù)制與原來的對(duì)象相同的值,而所有的對(duì)其他對(duì)象(不包含不可變類的對(duì)象)的引用仍然指向原來的對(duì)象。

    深復(fù)制

    相對(duì)于淺復(fù)制,更進(jìn)一步,深拷貝把要復(fù)制的對(duì)象所引用的對(duì)象都復(fù)制一遍。

    實(shí)現(xiàn)深復(fù)制有兩種方式。一種是繼續(xù)利用clone()方法,另一種是利用對(duì)象序列化。

    對(duì)于第一種方法,進(jìn)一步手動(dòng)將指向可變對(duì)象的引用再復(fù)制一遍即可。比如對(duì)于Sheep我們?cè)黾觗eepClone()方法,在該方法中明確將EarTag對(duì)象也復(fù)制一下。因此EarTag也需要重寫clone()方法。

    Sheep.java增加deepClone()方法

    public?Sheep?deepClone()?throws?CloneNotSupportedException?{Sheep?s?=?(Sheep)super.clone();s.setEarTag(s.getEarTag().clone());return?s; }

    EarTag.java增加clone()方法,別忘了實(shí)現(xiàn)Cloneable接口

    @Override public?EarTag?clone()?throws?CloneNotSupportedException?{return?(EarTag)?super.clone(); }

    這時(shí)候再測(cè)試一遍看輸出:

    簡(jiǎn)是一只5歲的多塞特白面綿羊,?它的×××色耳牌上寫著12345號(hào)。 克隆后... 多利是一只6歲的多塞特白面綿羊,?它的×××色耳牌上寫著12346號(hào)。 簡(jiǎn)是一只5歲的多塞特白面綿羊,?它的×××色耳牌上寫著12345號(hào)。 false

    可見,EarTag對(duì)象也被克隆了。

    這時(shí),其實(shí)還需要注意一個(gè)問題,我們這個(gè)例子中,EarTag的對(duì)象沒有指向其他對(duì)象的引用,假設(shè)有的話,是否要調(diào)用EarTag的deepClone()方法呢,如果是一個(gè)引用鏈,深度復(fù)制要達(dá)到什么樣的深度呢?是否有循環(huán)引用呢(比如EarTag中又有對(duì)Sheep的引用)?這都是在具體的使用過程中需要謹(jǐn)慎考慮的。

    第二種方法是通過對(duì)象序列化來實(shí)現(xiàn)對(duì)象的深克隆。在Sheep.java中增加如下方法:

    public?Sheep?serializedClone()?throws?IOException,?ClassNotFoundException?{ByteArrayOutputStream?bao?=?new?ByteArrayOutputStream();ObjectOutputStream?oo?=?new?ObjectOutputStream(bao);oo.writeObject(this);ByteArrayInputStream?bai?=?new?ByteArrayInputStream(bao.toByteArray());ObjectInputStream?oi?=?new?ObjectInputStream(bai);return?(Sheep)?oi.readObject(); }

    注意的是,Sheep和EarTag都需要實(shí)現(xiàn)Serializable接口,以便打開對(duì)序列化的支持。

    測(cè)試一下:

    Sheep?jane?=?new?Sheep("簡(jiǎn)",?5,?"多塞特白面綿羊",?new?EarTag(12345,?"×××")); System.out.println(jane); Sheep?dolly?=?jane.serializedClone(); System.out.println("克隆后..."); dolly.setName("多利"); dolly.setAge(6); dolly.getEarTag().setId(12346); System.out.println(dolly);System.out.println(jane); System.out.println(jane.getEarTag()?==?dolly.getEarTag());

    輸出如下:

    簡(jiǎn)是一只5歲的多塞特白面綿羊,?它的×××色耳牌上寫著12345號(hào)。 克隆后... 多利是一只6歲的多塞特白面綿羊,?它的×××色耳牌上寫著12346號(hào)。 簡(jiǎn)是一只5歲的多塞特白面綿羊,?它的×××色耳牌上寫著12345號(hào)。 false

    可見也確實(shí)實(shí)現(xiàn)了深克隆。

    通用約定2: x.clone().getClass() == x.getClass() 必須為真。

    指的是克隆后的對(duì)象其類型是一致的。這一點(diǎn)沒有問題,及時(shí)在有繼承關(guān)系的情況下。

    ClassA.java

    public?class?ClassA?implements?Cloneable?{public?int?getA()?{return?a;}public?void?setA(int?a)?{this.a?=?a;}private?int?a;@Overridepublic?ClassA?clone()?throws?CloneNotSupportedException?{return?(ClassA)?super.clone();} }

    ClassB.java(繼承ClassA)

    public?class?ClassB?extends?ClassA?{private?String?b;public?String?getB()?{return?b;}public?void?setB(String?b)?{this.b?=?b;}public?void?test()?{System.out.println(super.getClass().getCanonicalName());} }

    測(cè)試一下:

    ClassB?b?=?new?ClassB(); b.setA(1); b.setB("b"); ClassB?b1?=?(ClassB)?b.clone(); System.out.println(b1.getB());

    結(jié)果為:

    b

    可見,即使子類沒有重寫clone()方法,只要其各層父類中有重新了public的clone()方法的,那么clone()方法都能正確克隆調(diào)起該方法的對(duì)象,且類型正確。話說回來,畢竟clone()的動(dòng)作最終都是源于Object的那個(gè)native方法的。

    通用約定3: x.clone().equals(x)為真

    這一條并非強(qiáng)制約束,但盡量保證做到。因?yàn)閺囊话阏J(rèn)識(shí)上來講,克隆的兩個(gè)對(duì)象雖然是不相等(==)的,但應(yīng)該是相同(equal)的。

    重寫Sheep.java和EarTag.java的equals()方法:

    Sheep.java

    ????@Overridepublic?boolean?equals(Object?obj)?{if?(obj?==?this)return?true;if?(!(obj?instanceof?Sheep))return?false;Sheep?s?=?(Sheep)?obj;return?s.name.equals(this.name)?&&s.age?==?this.age?&&s.breed.equals(this.breed)?&&s.earTag.equals(this.earTag);}

    EarTag.java

    ????@Overridepublic?boolean?equals(Object?obj)?{if?(obj?==?this)return?true;if?(!(obj?instanceof?Sheep))return?false;Sheep?s?=?(Sheep)?obj;return?s.name.equals(this.name)?&&s.age?==?this.age?&&s.breed.equals(this.breed)?&&s.earTag.equals(this.earTag);}

    測(cè)試一下:

    Sheep?jane?=?new?Sheep("簡(jiǎn)",?5,?"多塞特白面綿羊",?new?EarTag(12345,?"×××")); Sheep?dolly?=?jane.serializedClone(); System.out.println("克隆后..."); System.out.println(jane.equals(dolly));

    輸出為true,表示兩個(gè)對(duì)象是相同的。

    總結(jié)

    最后,我們總結(jié)一下,實(shí)現(xiàn)clone的方法:?
    1)在派生類中實(shí)現(xiàn)Cloneable借口;?
    2)在派生類中覆蓋基類的clone方法,聲明為public;?
    3)在派生類的clone方法中,調(diào)用super.clone();?
    4)若要深克隆對(duì)象,則需要增加對(duì)引用為非不可變對(duì)象的克隆。


    轉(zhuǎn)載于:https://blog.51cto.com/liukang/1982920

    總結(jié)

    以上是生活随笔為你收集整理的Java设计模式百例(番外) - Java的clone的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

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