[Effective Java] (11) 謹(jǐn)慎地覆蓋clone

Cloneable接口的目的是作為對(duì)象的mixin接口(mixin interface),表明這樣的對(duì)象允許克隆(clone)。
不能僅僅因?yàn)橐粋€(gè)對(duì)象實(shí)現(xiàn)了Cloneable,就可以調(diào)用clone方法。
實(shí)現(xiàn)接口是為了表明類可以為客戶做些什么,然而對(duì)于Cloneable接口,它改變了超類中受保護(hù)的方法行為。

1. Cloneable接口的作用

該接口決定了Object中受保護(hù)的clone方法的實(shí)現(xiàn)行為:

  • 如果一個(gè)類實(shí)現(xiàn)了Cloneable接口,Object的clone方法返回該對(duì)象的逐域拷貝;
  • 如果一個(gè)類未實(shí)現(xiàn)Cloneable接口,則該對(duì)象就會(huì)拋出CloneNotSupportedException異常。
2. clone方法的約定
  • clone方法的約定通常是非常弱的,以下要求在clone方法中都不是絕對(duì)要求:
x.clone() != x;                             //將會(huì)是true
x.clone().getClass() == x.getClass();       //將會(huì)是true
x.clone().equals(x);                        //將會(huì)是true
  • 拷貝對(duì)象往往會(huì)導(dǎo)致創(chuàng)建它的類的一個(gè)新實(shí)例,但它同時(shí)也會(huì)要求拷貝內(nèi)部的數(shù)據(jù)結(jié)構(gòu),在這個(gè)過程中不許調(diào)用構(gòu)造器。
3. 實(shí)現(xiàn)clone方法
  • 超類能夠提供克隆功能的唯一途徑是返回一個(gè)通過調(diào)用super.clone而得到的對(duì)象;
  • 如果覆蓋了非final類中的clone方法,則應(yīng)該返回一個(gè)通過調(diào)用super.clone而得到的對(duì)象,如果類所有的超類都遵守這條規(guī)則那么調(diào)用super.clone最終會(huì)調(diào)用Object的clone方法,從而創(chuàng)造出正確類的實(shí)例。(機(jī)制為自動(dòng)調(diào)用鏈,非強(qiáng)制要求)
  • 如果待克隆的域僅僅包含一個(gè)基本類型的值,或者包含一個(gè)指向不可變對(duì)象的引用,則被返回的對(duì)象可能是正需要的對(duì)象,如果不是,需要另作處理
4. 非基本類型clone
  • Stack中的數(shù)組
public class Stack {
    private Object[] elements;
}

// clone時(shí)需要拷貝棧的內(nèi)部信息,

// elements數(shù)組遞歸調(diào)用clone方法
@Override
public Stack clone() {
    try {
        Stack result = (Stack) super.clone();
        result.elements = elements.clone();
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}

// 我們不一定將elements.clone()的結(jié)果轉(zhuǎn)換成Object[]。
// 自Java1.5發(fā)行版起,在數(shù)組上調(diào)用clone返回?cái)?shù)組的數(shù)組,其編譯時(shí)類型與被克隆數(shù)組的類型相同
// 注:如果elements域是final的,上述方案就不能正常工作,因?yàn)閏lone方法是被禁止給elements域賦新值
// (clone架構(gòu)與引用可變對(duì)象的final域的正常使用時(shí)不兼容的)
  • 散列桶中的數(shù)組
    正在為一個(gè)散列表編寫clone方法,它的內(nèi)部數(shù)據(jù)包含一個(gè)散列桶數(shù)組,每個(gè)散列桶都指向“鍵-值”對(duì)鏈表的第一個(gè)項(xiàng),如果桶是空的,則為null。該類如下:
public class HashTable implements Cloneable {
    private Entry[] buckets = ...;
    private static class Entry {
        final Object key;
        Object value;
        Entry next;
        Entry (Object key, Object value, Entry next) {
            this.key = key;
            this.value = value;
        }
    }
}

// clone時(shí)不僅需要遞歸的克隆這個(gè)散列桶數(shù)組,
// 而且需要單獨(dú)的拷貝并組成每個(gè)桶的列表,下面是常用做法

@Override
public HashTable clone() {
    try {
        HashTable result = (HashTable) super.clone();
        result.buckets = new Entry[buckets.length];
        for (int i = 0; i < buckets.length; i++) {
            if (buckets[i] != null) {
                result.buckets[i] = buckets[i].deepCopy();
            }
        }
    } catch (CloneNotSupportException e) {
        throw new AssertionError();
    }
}

// 私有類的HashTable.Entry被加強(qiáng)了,支持一個(gè)“深拷貝(deep copy)”方法
// 防止因鏈表過長(zhǎng)導(dǎo)致棧溢出,可采用迭代(Iteration)的方法代替遞歸(recursion)

// Iteratively copy the linked list headed by this Entry
Entry deepCopy() {
    Entry result = new Entry(key, value, next);
    for (Entry p = result; p.next != null; p = p.next) {
        p.next = new Entry(p.next.key, p.next.value, p.next.next);
    }
    return result;
}
  • 復(fù)雜對(duì)象的克隆
    先調(diào)用super.clone,然后把結(jié)果對(duì)象中的所有域設(shè)置成他們的空白狀態(tài)(virgin state),然后調(diào)用高層的方法來重新產(chǎn)生對(duì)象的狀態(tài)。這種做法往往會(huì)產(chǎn)生一個(gè)簡(jiǎn)單、合理且相當(dāng)優(yōu)美的clone方法,但通常沒有直接操作對(duì)象及其克隆對(duì)象的內(nèi)部狀態(tài)的clone方法快。

  • 總而言之,實(shí)現(xiàn)了Cloneable接口的類都應(yīng)該用一個(gè)共有的方法覆蓋clone。此公有方法首先調(diào)用super.clone,然后修正任何需要修改的域。

5. 代替方法
  • 另外實(shí)現(xiàn)對(duì)象拷貝的好辦法是提供一個(gè)拷貝構(gòu)造器(copy constructor)或拷貝工廠(copy factory)??截悩?gòu)造器只是一個(gè)構(gòu)造器,它唯一的參數(shù)類型是包含該構(gòu)造器的類。如:
public Yum(Yum yum);

// 拷貝工廠類似于拷貝構(gòu)造器的靜態(tài)工廠:
public static Yum newInstance(Yum yum);
  • 拷貝構(gòu)造器的優(yōu)點(diǎn)
  1. 其不依賴于某一種很有風(fēng)險(xiǎn)的、語(yǔ)言之外的對(duì)象創(chuàng)建機(jī)制;
  2. 其不遵守尚未制定好的文檔規(guī)范;
  3. 其不會(huì)與final域的正常使用發(fā)生沖突;
  4. 其不會(huì)拋出不必要的受檢查異常;
  5. 其不需要類型轉(zhuǎn)換;
  6. 采用其代替clone方法時(shí),并沒有放棄接口功能特性。

注:對(duì)于一個(gè)專門為了繼承而設(shè)計(jì)的類,如果你未能提供行為良好的受保護(hù)的clone方法,他的子類就不能實(shí)現(xiàn)Cloneable接口。

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

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

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