<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從原型模式到淺拷貝和深拷貝

          2020-08-05 21:48


          點擊藍色“JavaKeeper”關(guān)注我喲

          加個“星標”,一起成長,做牛逼閃閃的技術(shù)人

          問題

          如果你有一個對象, 并希望生成與其完全相同的一個復制品, 你該如何實現(xiàn)呢?

          首先, 你必須新建一個屬于相同類的對象。然后, 你必須遍歷原始對象的所有成員變量, 并將成員變量值復制到新對象中。

          for?(int?i?=?0;?i?10;?i++)?{
          ??Sheep?sheep?=?new?Sheep("肖恩"+i+"號",2+i,"白色");
          ??System.out.println(sheep.toString());
          }

          這種方式是比較容易想到的,但是有幾個不足

          • 在創(chuàng)建新對象的時候,總是需要重新獲取原始對象的屬性,如果創(chuàng)建的對象比較復雜,效率會很低
          • 總是需要重新初始化對象,而不是動態(tài)地獲得對象運行時的狀態(tài), 不夠靈活
          • 另一方面,并非所有對象都能通過這種方式進行復制, 因為有些對象可能擁有私有成員變量, 它們在對象本身以外是不可見的

          萬物兼對象的 Java 中的所有類的根類 Object,提供了一個 clone() 方法,該方法可以將一個 Java 對象復制一份,但是需要實現(xiàn) clone() 的類必須要實現(xiàn)一個接口 Cloneable,該接口表示該類能夠復制且具有復制的能力。

          這就引出了原型模式。

          基本介紹

          1. 原型模式(Prototype模式)是指:用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型,創(chuàng)建新的對象
          2. 原型模式是一種創(chuàng)建型設(shè)計模式, 使你能夠復制已有對象, 而又無需使代碼依賴它們所屬的類
          3. 工作原理是:通過將一個原型對象傳給那個要發(fā)動創(chuàng)建的對象,這個要發(fā)動創(chuàng)建的對象通過請求原型對象拷貝它們自己來實施創(chuàng)建,即 對象**.clone**()

          類圖

          • Prototype : 原型 (Prototype) 接口將對克隆方法進行聲明

            Java 中 Prototype 類需要具備以下兩個條件

            • 實現(xiàn) Cloneable 接口。在 Java 語言有一個 Cloneable 接口,它的作用只有一個,就是在運行時通知虛擬機可以安全地在實現(xiàn)了此接口的類上使用 clone 方法。在 Java 虛擬機中,只有實現(xiàn)了這個接口的類才可以被拷貝,否則在運行時會拋出 CloneNotSupportedException 異常
            • 重寫 Object 類中的 clone 方法。Java 中,所有類的父類都是 Object 類,Object 類中有一個 clone 方法,作用是返回對象的一個拷貝
          • ConcretePrototype:具體原型 (Concrete Prototype) 類將實現(xiàn)克隆方法。除了將原始對象的數(shù)據(jù)復制到克隆體中之外, 該方法有時還需處理克隆過程中的極端情況, 例如克隆關(guān)聯(lián)對象和梳理遞歸依賴等等。

          • Client: 使用原型的客戶端,首先要獲取到原型實例對象,然后通過原型實例克隆自己,從而創(chuàng)建一個新的對象。

          實例

          我們用王二小放羊的例子寫這個實例

          1、原型類(實現(xiàn) Clonable

          @Setter
          @Getter
          @NoArgsConstructor
          @AllArgsConstructor
          class?Sheep?implements?Cloneable?{
          ????private?String?name;
          ????private?Integer?age;
          ????private?String?color;

          ????@Override
          ????protected?Sheep?clone()?{
          ????????Sheep?sheep?=?null;
          ????????try?{
          ????????????sheep?=?(Sheep)?super.clone();
          ????????}?catch?(Exception?e)?{
          ????????????System.out.println(e.getMessage());
          ????????}
          ????????return?sheep;
          ????}
          }

          2、具體原型

          按業(yè)務(wù)的不同實現(xiàn)不同的原型對象,假設(shè)現(xiàn)在主角是王二小,羊群里有山羊、綿羊一大群

          public?class?Goat?extends?Sheep{
          ????public?void?graze()?{
          ????????System.out.println("山羊去吃草");
          ????}
          }
          public?class?Lamb?extends?Sheep{
          ????public?void?graze()?{
          ????????System.out.println("羔羊去吃草");
          ????}
          }

          3、客戶端

          public?class?Client?{

          ????static?List?sheepList?=?new?ArrayList<>();
          ????public?static?void?main(String[]?args)?{
          ????????Goat?goat?=?new?Goat();
          ????????goat.setName("山羊");
          ????????goat.setAge(3);
          ????????goat.setColor("灰色");
          ????????for?(int?i?=?0;?i?5;?i++)?{
          ????????????sheepList.add(goat.clone());
          ????????}

          ????????Lamb?lamb?=?new?Lamb();
          ????????lamb.setName("羔羊");
          ????????lamb.setAge(2);
          ????????lamb.setColor("白色");
          ????????for?(int?i?=?0;?i?5;?i++)?{
          ????????????sheepList.add(lamb.clone());
          ????????????System.out.println(lamb.hashCode()+","+lamb.clone().hashCode());
          ????????}

          ????????for?(Sheep?sheep?:?sheepList)?{
          ????????????System.out.println(sheep.toString());
          ????????}
          }

          原型模式將克隆過程委派給被克隆的實際對象。模式為所有支持克隆的對象聲明了一個通用接口, 該接口讓你能夠克隆對象,同時又無需將代碼和對象所屬類耦合。通常情況下,這樣的接口中僅包含一個 克隆方法。

          所有的類對 克隆方法的實現(xiàn)都非常相似。該方法會創(chuàng)建一個當前類的對象, 然后將原始對象所有的成員變量值復制到新建的類中。你甚至可以復制私有成員變量, 因為絕大部分編程語言都允許對象訪問其同類對象的私有成員變量。

          支持克隆的對象即為原型。當你的對象有幾十個成員變量和幾百種類型時, 對其進行克隆甚至可以代替子類的構(gòu)造。

          優(yōu)勢

          使用原型模式創(chuàng)建對象比直接 new 一個對象在性能上要好的多,因為 Object 類的 clone 方法是一個本地方法,它直接操作內(nèi)存中的二進制流,特別是復制大對象時,性能的差別非常明顯。

          使用原型模式的另一個好處是簡化對象的創(chuàng)建,使得創(chuàng)建對象就像我們在編輯文檔時的復制粘貼一樣簡單。

          因為以上優(yōu)點,所以在需要重復地創(chuàng)建相似對象時可以考慮使用原型模式。比如需要在一個循環(huán)體內(nèi)創(chuàng)建對象,假如對象創(chuàng)建過程比較復雜或者循環(huán)次數(shù)很多的話,使用原型模式不但可以簡化創(chuàng)建過程,而且可以使系統(tǒng)的整體性能提高很多。

          適用場景

          《Head First 設(shè)計模式》是這么形容原型模式的:當創(chuàng)建給定類的實例的過程很昂貴或很復雜時,就是用原型模式。

          如果你需要復制一些對象,同時又希望代碼獨立于這些對象所屬的具體類,可以使用原型模式。

          如果子類的區(qū)別僅在于其對象的初始化方式, 那么你可以使用該模式來減少子類的數(shù)量。別人創(chuàng)建這些子類的目的可能是為了創(chuàng)建特定類型的對象

          原型模式在 Spring 中的應(yīng)用

          我們都知道 Spring bean 默認是單例的,但是有些場景可能需要原型范圍,如下

          <bean?id="sheep"?class="priv.starfish.prototype.Sheep"?scope="prototype">
          ???<property?name="name"?value="肖恩"/>
          ???<property?name="age"?value="2"/>
          ???<property?name="color"?value="白色"/>
          bean>

          同樣,王二小還是有 10 只羊,感興趣的也可以看下他們創(chuàng)建的對象是不是同一個

          public?class?Client?{
          ????public?static?void?main(String[]?args)?{
          ????????ApplicationContext?context?=?new?ClassPathXmlApplicationContext("applicationContext.xml");
          ????????for?(int?i?=?0;?i?10;?i++)?{
          ????????????Object?bean?=?context.getBean("sheep");
          ????????????System.out.println(bean);
          ????????}
          ????}
          }

          感興趣的同學可以深入源碼看下具體的實現(xiàn),在 AbstractBeanFactory 的 doGetBean() 方法中

          原型模式的注意事項

          • 使用原型模式復制對象不會調(diào)用類的構(gòu)造方法。因為對象的復制是通過調(diào)用 Object 類的 clone 方法來完成的,它直接在內(nèi)存中復制數(shù)據(jù),因此不會調(diào)用到類的構(gòu)造方法。不但構(gòu)造方法中的代碼不會執(zhí)行,甚至連訪問權(quán)限都對原型模式無效。還記得單例模式嗎?單例模式中,只要將構(gòu)造方法的訪問權(quán)限設(shè)置為 private 型,就可以實現(xiàn)單例。但是 clone 方法直接無視構(gòu)造方法的權(quán)限,所以,單例模式與原型模式是沖突的,在使用時要特別注意。
          • 深拷貝與淺拷貝。Object 類的 clone方法只會拷貝對象中的基本的數(shù)據(jù)類型,對于數(shù)組、容器對象、引用對象等都不會拷貝,這就是淺拷貝。如果要實現(xiàn)深拷貝,必須將原型模式中的數(shù)組、容器對象、引用對象等另行拷貝。

          淺拷貝和深拷貝

          首先需要明白,淺拷貝和深拷貝都是針對一個已有對象的操作。

          在 Java 中,除了基本數(shù)據(jù)類型(元類型)之外,還存在 類的實例對象 這個引用數(shù)據(jù)類型。而一般使用 『 = 』號做賦值操作的時候。對于基本數(shù)據(jù)類型,實際上是拷貝的它的值,但是對于對象而言,其實賦值的只是這個對象的引用,將原對象的引用傳遞過去,他們實際上還是指向的同一個對象。

          而淺拷貝和深拷貝就是在這個基礎(chǔ)之上做的區(qū)分,如果在拷貝這個對象的時候,只對基本數(shù)據(jù)類型進行了拷貝,而對引用數(shù)據(jù)類型只是進行了引用的傳遞,而沒有真實的創(chuàng)建一個新的對象,則認為是淺拷貝。反之,在對引用數(shù)據(jù)類型進行拷貝的時候,創(chuàng)建了一個新的對象,并且復制其內(nèi)的成員變量,則認為是深拷貝。

          所謂的淺拷貝和深拷貝,只是在拷貝對象的時候,對 類的實例對象 這種引用數(shù)據(jù)類型的不同操作而已

          淺拷貝

          1. 對于數(shù)據(jù)類型是基本數(shù)據(jù)類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值復制一份給新的對象。

          2. 對于數(shù)據(jù)類型是引用數(shù)據(jù)類型的成員變量,比如說成員變量是某個數(shù)組、某個類的對象等,那么淺拷貝會進行引用傳遞,也就是只是將該成員變量的引用值(內(nèi)存地址)復制一份給新的對象。因為實際上兩個對象的該成員變量都指向同一個實例。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的該成員變量值

          3. 前面我們克隆羊就是淺拷貝,如果我們在 Sheep 中加一個對象類型的屬性,public Sheep child;可以看到 s 和 s1 的 friend 是同一個。

          ??Sheep?s?=?new?Sheep();
          ??s.setName("sss");
          ??
          ??s.friend?=?new?Sheep();
          ??s.friend.setName("喜洋洋");
          ??
          ??Sheep?s1?=?s.clone();
          ??System.out.println(s?==?s1);
          ??System.out.println(s.hashCode()+"---"+s.clone().hashCode());
          ??
          ??System.out.println(s.friend?==?s1.friend);
          ??System.out.println(s.friend.hashCode()?+?"---"?+s1.friend.hashCode());
          false
          621009875---1265094477
          true
          2125039532---2125039532

          深拷貝

          現(xiàn)在我們知道 clone() 方法,只能對當前對象進行淺拷貝,引用類型依然是在傳遞引用。那如何進行一個深拷貝呢?

          常見的深拷貝實現(xiàn)方式有兩種:

          1. 重寫 clone 方法來實現(xiàn)深拷貝
          2. 通過對象序列化實現(xiàn)深拷貝

          淺拷貝和深拷貝只是相對的,如果一個對象內(nèi)部只有基本數(shù)據(jù)類型,那用 clone() 方法獲取到的就是這個對象的深拷貝,而如果其內(nèi)部還有引用數(shù)據(jù)類型,那用 clone() 方法就是一次淺拷貝的操作。

          1.?人人都能看懂的 6 種限流實現(xiàn)方案!

          2.?一個空格引發(fā)的“慘案“

          3.?大型網(wǎng)站架構(gòu)演化發(fā)展歷程

          4.?Java語言“坑爹”排行榜TOP 10

          5. 我是一個Java類(附帶精彩吐槽)

          6. 看完這篇Redis緩存三大問題,保你能和面試官互扯

          7. 程序員必知的 89 個操作系統(tǒng)核心概念

          8. 深入理解 MySQL:快速學會分析SQL執(zhí)行效率

          9. API 接口設(shè)計規(guī)范

          10. Spring Boot 面試,一個問題就干趴下了!



          掃碼二維碼關(guān)注我


          ·end·

          —如果本文有幫助,請分享到朋友圈吧—

          我們一起愉快的玩耍!



          你點的每個贊,我都認真當成了喜歡

          瀏覽 5
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  韩国三级片网站播放 | 色哟哟一区二区三区四区 | 国产伊人网 | 午夜激情操一操 | 久久影院av无码 卡一卡二在线视频 |