java中clone方法的實現

java中僅有的創建對象的兩種方式:①.使用new操作符創建對象;②.使用clone方法復制對象。由于clone方法將最終將調用JVM中的原生方法完成復制,所以一般使用clone方法復制對象要比新建一個對象然后逐一進行元素復制效率要高。

淺拷貝與深拷貝

在java中基本數據類型是按值傳遞的,而對象是按引用傳遞的。所以當調用對象的clone方法進行對象復制時將涉及深拷貝和淺拷貝的概念。

淺拷貝是指拷貝對象時僅僅拷貝對象本身(包括對象中的基本變量),而不拷貝對象包含的引用指向的對象。深拷貝不僅拷貝對象本身,而且拷貝對象包含的引用指向的所有對象。通過clone方法復制對象時,若不對clone()方法進行改寫,則調用此方法得到的對象為淺拷貝。

例如:淺拷貝


    public class Stack implements Cloneable {
        private Object[] elements;
        private int size = 0;
        private static final int DEFAULT_INITIAL_CAPACITY = 16;

        public Stack() {
            elements = new Object[DEFAULT_INITIAL_CAPACITY];
        }

        public void push(Object o) {
            ensureCapacity();
            elements[size++] = o;
        }

        public Object pop() {
            if (size == 0)
                throw new EmptyStackException();
            Object result = elements[--size];
            elements[size] = null; // 【避免內存泄漏】
            return result;
        }

        private void ensureCapacity() {
            if (elements.length == size) {
                elements = Arrays.copyOf(elements, 2 * size + 1);
            }
        }

        // 實現clone方法,淺拷貝
        @Override
        protected Stack clone() throws CloneNotSupportedException {

            return (Stack) super.clone();
        }
    }

深拷貝:

    
    //深拷貝
    @Override
    protected Stack clone() throws CloneNotSupportedException {
        Stack result = (Stack) super.clone();
        result.elements = elements.clone(); //對elements元素進行拷貝(引用或基本數據類型)
        return result;
    }

其原理圖:


深拷貝與淺拷貝的原理

注意:

  • 由于java5.0后引入了協變返回類型(covariant return type)實現(基于泛型),即覆蓋方法的返回類型可以是被覆蓋方法的返回類型的子類型,所以clone方法可以直接返回Stack類型,而不用返回Object類型,然后客戶端再強轉。
  • 在數組上調用clone返回的數組,其編譯時類型與被克隆數組的類型相同。
  • 若elements域是final的,深拷貝不能正常工作。因為clone架構與引用可變對象的final域的正常用法是不兼容的。
  • 若elements數組中的元素是引用類型,則此方法僅僅是對引用的拷貝,元素指向的還是原來的對象

還應該注意,數組的clone,僅僅復制的是數組中的元素,即若數組中元素為引用類型,僅僅復制引用。若clone的對象中含有鏈表,則應單獨對鏈表進行循環復制。例如,一個內部包含一個散列桶數組的散列表,其數組中每個元素都指向一個獨立的鏈表。此時僅僅使用上面的方法就是不完全拷貝。

代碼:


    public class HashTable implements Cloneable {

        private static final int CAPACITY = 10;

        //散列桶數組,數組中元素指向由Entry對象組成的鏈表(指向鏈表第一個Entry)
        private Entry[] buckerts = new Entry[CAPACITY];

        public void put(Object key, Object value) {
            int index = key.hashCode() % CAPACITY;
            Entry e = buckerts[index];
            buckerts[index] = new Entry(key,value,e);
        }

        @Override
        public HashTable clone() throws CloneNotSupportedException {
            HashTable result = (HashTable)super.clone();
            result.buckerts = buckerts.clone(); //僅僅復制了對鏈表的引用。
            return result;
        }

        //輕量級單鏈表
        private static class Entry {
            final Object key;
            Object value;
            Entry next;

            Entry(Object key, Object value, Entry next) {
                this.key = key;
                this.value = value;
                this.next = next;
            }
        }
    }

原理圖:


不完全拷貝

雖然被克隆對象有自己的散列桶數組,但數組引用的鏈表與原對象是一樣的。數組的clone方法,僅僅拷貝了對鏈表的引用,而沒有復制鏈表中的元素。

改進代碼:


    @Override
    public HashTable clone() throws CloneNotSupportedException {
        HashTable result = (HashTable)super.clone();
        result.buckerts = buckerts.clone();
        for(int i=0; i<buckerts.length; i++) {
            result.buckerts[i] = buckerts[i].deepCopy();
        }
        return result;
    }

    //輕量級單鏈表
    private static class Entry {
        final Object key;
        Object value;
        Entry next;

        Entry(Object key, Object value, Entry next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        //遞歸實現鏈表復制
        Entry deepCopy() {
            return new Entry(key,value,next == null ? null : next.deepCopy());
        }
    }

在內部類Entry中的深度拷貝方法遞歸的調用自身,以完成鏈表的拷貝。雖然這種方法比較簡潔,但如果鏈表很長,有可能會導致棧溢出。可以使用迭代代替遞歸實現鏈表的復制。代碼如下:


    //迭代實現鏈表復制
    Entry deepCopy() {
        Entry result = new Entry(key, value, next);
        for(Entry e = result; e.next != null; e = e.next) {
            e.next = new Entry(e.next.key, e.next.value, e.next.next);
        }
        return result;
    }

實現clone方法的步驟:

  • 首先調用父類的super.clone方法(父類必須實現clone方法),這個方法將最終調用Object的中native型的clone方法完成淺拷貝
  • 對類中的引用類型進行單獨拷貝
  • 檢查clone中是否有不完全拷貝(例如,鏈表),進行額外的復制

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容

  • java筆記第一天 == 和 equals ==比較的比較的是兩個變量的值是否相等,對于引用型變量表示的是兩個變量...
    jmychou閱讀 1,511評論 0 3
  • 相關概念 面向對象的三個特征 封裝,繼承,多態.這個應該是人人皆知.有時候也會加上抽象. 多態的好處 允許不同類對...
    東經315度閱讀 1,973評論 0 8
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • 前幾天,有朋友去面試之前問我關于后端架構相關的問題,但奈于我去年更多的工作是在移動SDK開發上,對此有所遺忘,實屬...
    涅槃1992閱讀 5,254評論 8 76
  • 說實話,這次是我長這么大以來,第一次和爸爸看的電影。看的《天將雄獅》,爸爸自從我出生后就很少去過電影院了,難...
    WavePEACH閱讀 1,124評論 10 18