設計模式之原型模式,及深淺拷貝

設計模式,不管是在準備面試過程,還是在實際工作編碼中,都是應該掌握的技能,所以特別整理一下設計模式相關(guān)的知識,希望對大家有所幫助

可能我們會遇到這樣一種場景:在編碼中有這樣一個對象,此對象比較重(構(gòu)造比較耗時)或者是構(gòu)造方法相當復雜,使用起來很麻煩,總之創(chuàng)建成本很高,類似這樣


public class HeavyObject {

    public HeavyObject(){
        try {
            // 模擬次對象創(chuàng)建耗時
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("HeavyObject init success");
    }
}

而這種對象可能由于業(yè)務的需求,需要在循環(huán)中創(chuàng)建并使用

public class PrototypeTest {

    public static void main(String[] args) {
        long l1 = System.currentTimeMillis();
        for(int i = 0; i < 10; i ++) {
            HeavyObject heavyObject = new HeavyObject();
            System.out.println(heavyObject);
        }
        long l2 = System.currentTimeMillis();
        System.out.println(l1 - l2);
    }
}

上面這段代碼的效率相信不用執(zhí)行大家也知道是多大慘不忍睹,執(zhí)行測試,在我機器上為:10006ms;而針對這種情況能不能有一個好的方式來優(yōu)化呢?當然是今天的主角:原型模式啦。雖然號稱是“設計模式”,但是原型模式在實現(xiàn)的角度看確實很簡單:實現(xiàn)Cloneable接口即可,然后再獲取新的對象實例的時候使用原實例對象的clone()即可;
HeavyObject 變化為

public class HeavyObject implements Cloneable {

    public HeavyObject(){
        try {
            // 模擬次對象創(chuàng)建耗時
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("HeavyObject init success");
    }
}

測試方法

public class PrototypeTest {
    
    public static void main(String[] args) throws CloneNotSupportedException {
        long l1 = System.currentTimeMillis();
        HeavyObject heavyObject = new HeavyObject();
        for(int i = 0; i < 10; i ++) {
            // 通過clone獲取新的對象
            HeavyObject cloneHeavyObject = (HeavyObject) heavyObject.clone();
            System.out.println(cloneHeavyObject);
        }
        long l2 = System.currentTimeMillis();
        System.out.println(l1 - l2);
    }
}

此時測試結(jié)果為1000ms,耗時主要在為初次構(gòu)造HeavyObject 實例時構(gòu)造方法耗費,而且結(jié)果中,只打印了一次HeavyObject 構(gòu)造方法中的"HeavyObject init success"可以印證這一點,可見使用clone創(chuàng)建對象實例時沒有調(diào)用構(gòu)造方法,實際上是通過二進制流的方式只能讀取源數(shù)據(jù)并生成新的對象(Object中的native方法);
原型模式創(chuàng)建實例的方式看起來簡單而美好,但是有時候也是有些小坑的。比如我們的HeavyObject 中有List成員變量

public class HeavyObject implements Cloneable {

    List list;

    public List getList() {
        return list;
    }

    public void setList(List list) {
        this.list = list;
    }

   // ... 省略重復代碼

而我們使用的業(yè)務場景是這樣的

public class PrototypeTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        HeavyObject heavyObject = new HeavyObject();
        List list = new ArrayList();
        list.add(1);
        heavyObject.setList(list);
        // 通過clone拷貝對象
        HeavyObject cloneHeavyObject = (HeavyObject) heavyObject.clone();
        // 模擬業(yè)務場景,對clone的對象中的list進行操作
        cloneHeavyObject.getList().remove(0);
        System.out.println(heavyObject.getList());
    }

執(zhí)行結(jié)果

HeavyObject init success
[]

Process finished with exit code 0

可見,對clone對象成員變量進行操作,影響了原對象。我們在最后一行打斷點,看一下


image.png

上面的原對象和下面的clone對象的成員變量list,指向的對象都是ArrayList@455,所以造成操作clone對象的List而影響了原對象,這種拷貝對象的方式,也被成為淺拷貝,淺拷貝就是將原對象直接拷貝一份,原對象中的成員變量的指針當然也拷貝過來,所以該例中的兩個對象的list仍舊指向同一個對象;怎么解決呢?其實jdk早給給我們了答案,我們翻看一下ArrayList,發(fā)現(xiàn)它也實現(xiàn)了Cloneable接口的,然后看下其clone()

public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

ArrayList答題實現(xiàn)思路是將ArrayList本身拷貝后,將其成員變量elementData ,通過Arrays方法重寫拷貝一份賦值,我們嘗試把HeavyObject的clone()這么改,即新創(chuàng)建一個List對象

@Override
    protected Object clone() throws CloneNotSupportedException {
        HeavyObject cloneHeavyObject = (HeavyObject) super.clone();
        cloneHeavyObject.list = new ArrayList(this.list);
        return cloneHeavyObject;
    }

在執(zhí)行測試方法結(jié)果

HeavyObject init success
[1]

Process finished with exit code 0

上面這種,除了拷貝對象本身以為,還將對象中其他對象也拷貝一份新的對象,也就是我們常說的深拷貝。其實上例實現(xiàn)的深拷貝,也是有問題的,比如List承載的元素依舊是引用類型,而我的拷貝方式是直接將原List中的對象拿過來加到新的List中,所以雖然List對象是新創(chuàng)建的,但其中元素依舊指向同一個對象,所以業(yè)務有需求時應繼續(xù)給List中對象也繼續(xù)拷貝一份新的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容