Effective Java 3rd 條目13 謹(jǐn)慎覆寫clone

Cloneable接口目的是作為類的一個混入接口(mixin interface)(條目20)考蕾,宣稱這些允許克隆知残。不幸的是文判,它未能作為這個目的。它的主要缺點(diǎn)在于忆肾,缺少了clone方法荸频,而且Object的clone方法是受保護(hù)的。沒有訴諸于反射(reflection)(條目65)客冈,你不能僅僅因?yàn)樗鼘?shí)現(xiàn)了Cloneable而調(diào)用對象的clone旭从。甚至反射調(diào)用可能失敗,因?yàn)闆]有保證對象有一個可獲取的clone方法场仲。盡管這個缺點(diǎn)和其他的缺點(diǎn)和悦,這個技巧在合理的廣泛使用中,所以理解它是值得的渠缕。這個條目告訴你怎么實(shí)現(xiàn)一個行為良好的clone方法鸽素,在合適的時候討論,而且提出替代方法亦鳞。

考慮到Cloneable沒有包含任何方法馍忽,那么它做什么的呢?它決定了Object的受保護(hù)的clone實(shí)現(xiàn)的行為:如果一個類實(shí)現(xiàn)了Cloneable燕差,Object的clone方法返回這個對象逐個域的拷貝遭笋;否則它拋出CloneNotSupportedException。這是接口的非常不典型的用法徒探,而且不是應(yīng)該效仿的一個瓦呼。通常地,實(shí)現(xiàn)一個接口說明一個類能為它的客戶端做什么测暗。這個情形中央串,它改變了超類的受保護(hù)方法的行為。

盡管文檔沒有說碗啄,在實(shí)踐中质和,一個實(shí)現(xiàn)了Cloneable的類被期待提供一個正常功能的公開clone方法。為了達(dá)到這個目的挫掏,這個類和它的所有超類必須遵從一個復(fù)雜的侦另、非強(qiáng)制的和輕文檔的協(xié)議。這個最終機(jī)制是脆落的、危險(xiǎn)的和超語言的(extralinguistic):它創(chuàng)建了對象而沒有調(diào)用一個構(gòu)造子褒傅。

clone方法的通用協(xié)定是不牢固的弃锐。就在這里,從Object規(guī)范拷貝的:

創(chuàng)建和返回這個對象的一個拷貝殿托∨眨“拷貝”的確切含義可能取決于這個對象的類。通常的意圖是支竹,對于任意對象x旋廷,表達(dá)式
x.clone() != x
將會是真,而且表達(dá)式
x.clone().getClass() == x.getClass()
將會是真礼搁,但是這些不是絕對的要求饶碘。雖然通常情況是這樣
x.clone().equals(x)
將會是真,但是這不是絕對的要求馒吴。
按照慣例扎运,這個方法返回的對象應(yīng)該通過調(diào)用super.clone獲得。如果一個類和它的所有超類(除了Object)遵從這個慣例饮戳,它將會是這個情形
x.clone().getClass() == x.getClass()
按照慣例豪治,返回的對象應(yīng)該獨(dú)立于克隆的對象。為了獲得這個獨(dú)立扯罐,在對象返回之前负拟,應(yīng)該有必要改變super.clone返回對象的一個或者多個域。

這個機(jī)制與構(gòu)造子鏈?zhǔn)巧晕⑾嗨频拇鹾樱诉@不是強(qiáng)制的:如果一個類的clone方法返回了一個實(shí)例掩浙,這個實(shí)例不是通過調(diào)用super.clone獲取的,而是通過調(diào)用一個構(gòu)造子秸歧,那么編譯器不會報(bào)錯涣脚,但是如果那個類的一個子類調(diào)用super.clone,最終的對象將有錯誤的類寥茫,這阻止了clone方法正常工作。如果一個覆寫了clone方法的類是final的矾麻,這個慣例可以安全地忽略纱耻,因?yàn)闆]有子類可以擔(dān)心。但是如果一個final類有一clone方法险耀,它沒有調(diào)動super.clone弄喘,那么沒有理由為這個類實(shí)現(xiàn)Cloneable,因?yàn)樗鼪]有依賴于Object的clone實(shí)現(xiàn)的行為甩牺。

假設(shè)一個類的超類提供了一個行為良好的clone方法蘑志,你想在這個類中實(shí)現(xiàn)Cloneable。首先,調(diào)用super.clone急但。你取回的對象將會是原件的完整功能的副本澎媒。在你的類中聲明的任何域,將有相等于原件那些域的值波桩,如果每個域含有一個初始值戒努,或者不可變對象的應(yīng)用,那么返回的對象可能就是你想要的镐躲,在這個情形中沒有必要進(jìn)一步處理储玫。比如,這是條目11中的PhoneNumber類的情形萤皂,但是注意撒穷,不可變的類應(yīng)該從不提供一個clone方法,因?yàn)樗鼉H僅會鼓勵浪費(fèi)拷貝裆熙。盡管如此端礼,下面是PhoneNumber一個clone方法的樣子:

// clone方法,為沒有可變狀態(tài)的引用這個類
@Override public PhoneNumber clone() { 
    try { 
        return (PhoneNumber) super.clone(); 
    } catch (CloneNotSupportedException e) { 
        throw new AssertionError(); // Can't happen 
    } 
}

為了這個方法起作用弛车,PhoneNumber類的聲明將被修改為表明它實(shí)現(xiàn)了Cloneable齐媒。盡管Object的clone方法返回Object,但是這個clone方法返回PhoneNumber纷跛。這樣做是合法和合理的喻括,因?yàn)镴ava支持協(xié)變返回類型(covariant return type)。換句話說贫奠,覆寫方法的返回類型可以是被覆寫方法返回類型的一個子類型唬血。這樣不需要在客戶端強(qiáng)轉(zhuǎn)。在返回Object的super.clone的返回值之前唤崭,我們必須強(qiáng)轉(zhuǎn)它拷恨,而且強(qiáng)轉(zhuǎn)保證一定成功。

super.clone的調(diào)用包含在一個try-catch代碼塊中谢肾。這是因?yàn)镺bject聲明它的clone方法拋出了CloneNotSupportedException腕侄,這是個受檢查的異常。因?yàn)镻honeNumber實(shí)現(xiàn)了Cloneable芦疏,所以我們知道super.clone的調(diào)用將會成功冕杠。樣板代碼的必要性表明CloneNotSupportedException應(yīng)該是沒有受檢查的(條目71)。

如果一個對象含有引用可變對象的域酸茴,那么上面顯示的簡單的克隆實(shí)現(xiàn)可能是災(zāi)難性的分预。比如,考慮條目7的Stack類:

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

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

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

    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); 
    }
}

假設(shè)你想使得這個類是可克隆的薪捍。如果clone方法僅僅返回super.clone()笼痹,那么產(chǎn)生的Stack實(shí)例將會在它的size域有正確的值配喳,但是它的elements域,將會引用與原來Stack實(shí)例一樣的隊(duì)列凳干。修改原件將會在克隆中破壞了這個不變量晴裹,反之亦然。你將很快發(fā)現(xiàn)纺座,你的程序產(chǎn)生了無意義的結(jié)果或者拋出一個NullPointerException息拜。

這種情形應(yīng)該永遠(yuǎn)不會發(fā)生,作為調(diào)用Stack類的唯一構(gòu)造子的結(jié)果净响。實(shí)際上少欺,clone方法是起一個構(gòu)造子的作用;你必須保證它不會影響原來的對象馋贤,而且保證它在克隆上正確地創(chuàng)建不變量赞别。為了Stack的clone方法正常工作,它必須拷貝Stack的內(nèi)部組件配乓。這么做最容易的方式是在elements隊(duì)列上遞歸地調(diào)用clone方法:

// Clone method for class with references to mutable state 
@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é)果強(qiáng)轉(zhuǎn)到Object[]。在一個隊(duì)列上調(diào)用clone方法返回一個隊(duì)列犹芹,它的運(yùn)行時和編譯時的類型崎页,同被克隆的隊(duì)列的那些類型是相同的。這是復(fù)制一個隊(duì)列的優(yōu)選習(xí)慣用法腰埂。事實(shí)上飒焦,隊(duì)列是clone附加功能的唯一強(qiáng)制使用。

同時注意到屿笼,如果elements域是final的牺荠,前面的解決方法不起作用,因?yàn)閏lone被禁止賦新值到域驴一。這是一個基本的問題:就像系列化休雌,Cloneable架構(gòu),與引用可變對象的final域的正常使用肝断,是不相容的杈曲,除了在這些情況下:可變對象可以在對象和它的克隆之間安全地共享。為了使得一個類可克隆胸懈,移除一些域的final修飾符可能是必要的鱼蝉。

僅僅遞歸地調(diào)用clone不總是足夠的。比如箫荡,假設(shè)你為哈希表編寫一個clone方法,它的內(nèi)部有桶隊(duì)列組成渔隶,每個桶引用鍵值對鏈表中的第一項(xiàng)羔挡。為了性能洁奈,類單獨(dú)實(shí)現(xiàn)了它自己的輕量級鏈表,而不是內(nèi)部使用java.util.LinkedList:

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; 
            this.next = next; 
        }
    } 
    ... // 其余省略
}

假設(shè)你僅僅遞歸克隆桶隊(duì)列绞灼,就像我們已經(jīng)為Stack做的:

// 已破壞的clone方法 - 導(dǎo)致共享的可變狀態(tài)利术!
@Override public HashTable clone() {
    try { 
        HashTable result = (HashTable) super.clone(); 
        result.buckets = buckets.clone(); 
        return result; 
    } catch (CloneNotSupportedException e) { 
        throw new AssertionError(); 
    }
}

盡管克隆有它自己桶隊(duì)列,但是這個隊(duì)列與原來的隊(duì)列引用同一個鏈表低矮,這可能在克隆和原件中容易造成不確定的行為印叁。為了解決這個問題,你不得不拷貝由每個桶組成的鏈表军掂。下面是一個通用的方法:

// 復(fù)雜可變狀態(tài)類的遞歸clone方法
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;
            this.next  = next;  
        }
        // 遞歸地拷貝由這個Entry開頭的鏈表
        Entry deepCopy() {
            return new Entry(key, value,
                next == null ? null : next.deepCopy());
        }
    }

    @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();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
    ... //其余省略
}

私有類HashTable.Entry被增強(qiáng)為支持一個“深度拷貝”方法轮蜕。HashTable的clone方法,分配了新的恰當(dāng)大小的一個桶隊(duì)列蝗锥,遍歷了原來的桶隊(duì)列跃洛,深拷貝了每個非空桶。Entry的deepCopy方法终议,遞歸地調(diào)用自己來拷貝由這個項(xiàng)開頭的整個鏈表汇竭。如果桶不是太長,這個技巧是較好的而且工作正常穴张,但是這不是克隆鏈表的一個好方式细燎,因?yàn)樗鼮榱斜碇忻總€元素消耗了一個棧幀。如果列表很長皂甘,這可能容易造成棧溢出玻驻。為了防止這個發(fā)生,你可以用迭代代替deepCopy里面的遞歸:

// 迭代地拷貝由這個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ù)雜可變對象的最后方案是調(diào)用super.clone叮贩,設(shè)置最終對象中的所有域?yàn)樗鼈兊某跏紶顟B(tài)击狮,然后調(diào)用更高層的方法重新生成原來對象的狀態(tài)。對于我們HashTable例子的情形,buckets域?qū)跏蓟癁橐粋€新的桶隊(duì)列拜姿,而且將會為克隆哈希表中的每個鍵值映射朦佩,調(diào)用put(key, value)方法(沒有展示出來)。這個方法通常是一個簡單而非常優(yōu)雅的clone方法档冬,它不會像直接操作克隆內(nèi)部結(jié)構(gòu)的clone方法運(yùn)行那么快。雖然這個方法簡潔桃纯,但是與整個Cloneable結(jié)構(gòu)是相違背的酷誓,因?yàn)樗つ康馗采w逐個域的對象拷貝,它形成了這個架構(gòu)的基礎(chǔ)态坦。

就像構(gòu)造子盐数,clone方法在創(chuàng)建中決不應(yīng)該在克隆上調(diào)用一個可覆寫的方法(條目19)。如果clone調(diào)用一個被子類覆寫的方法伞梯,在子類有機(jī)會克隆中修改它的狀態(tài)之前玫氢,這個方法將會執(zhí)行帚屉,這很可能導(dǎo)致在克隆和原件中的損壞。所以漾峡,前面段落討論的put(key, value)方法攻旦,應(yīng)該是final或者私有的。(如果它是私有的生逸,那么它大概是非final公開方法的“helper方法”牢屋。)

Object的clone方法聲明為拋出CloneNotSupportedException,但是覆寫方法是不必要的槽袄。公開的clone方法應(yīng)該省略throws子句烙无,因?yàn)椴粫伋鍪軝z查異常的方法更容易使用(條目71)。

當(dāng)為繼承而設(shè)計(jì)類的時候你有兩個選擇(條目19)掰伸,但是不管你選擇哪個皱炉,類不應(yīng)該實(shí)現(xiàn)Cloneable。你可能選擇通過實(shí)現(xiàn)一個方法模擬Object的行為狮鸭,這個方法是正常功能的受保護(hù)clone方法合搅,而且clone方法是被聲明為拋出CloneNotSupportedException。這給子類自由:是否實(shí)現(xiàn)Cloneable歧蕉,就像它們直接擴(kuò)展了Object灾部。或者惯退,你可以選擇不實(shí)現(xiàn)一個可行的clone方法赌髓,而且通過提供如下的反生成clone實(shí)現(xiàn)阻止子類實(shí)現(xiàn)這個接口:

//不支持Cloneable的可擴(kuò)展類的clone方法 
@Override 
protected final Object clone() throws CloneNotSupportedException { 
    throw new CloneNotSupportedException(); 
}

另一個細(xì)節(jié)需要注意。如果你編寫實(shí)現(xiàn)了Cloneable的線程安全類催跪,記住它的clone方法必須正確地同步锁蠕,就像其他的方法(條目87)。Objective的clone方法不是同步的懊蒸,所以即使它的實(shí)現(xiàn)在其他方法是滿足的荣倾,但是你可能必須編寫返回super.clone()的一個同步clone方法。

簡要概括骑丸,所有實(shí)現(xiàn)Cloneable的類應(yīng)該用公開方法覆寫克隆舌仍,這個公開方法返回類型是這個類本身。這個方法應(yīng)該首先調(diào)用super.clone通危,然后修改需要修改的域铸豁。通常這意味著,拷貝任何由對象內(nèi)部“深層結(jié)構(gòu)”組成的可變對象菊碟,而且用它們拷貝的引用代替clone的這些對象的引用节芥。雖然這些內(nèi)部拷貝經(jīng)常可以遞歸調(diào)用克隆逆害,但是這不總是最好的方案藏古。如果類包含了只有原始類型的域或者對不可變對象的引用增炭,那么域很可能不需要修改。這個規(guī)律也有例外情況拧晕。比如,一個代表系列碼或者其它唯一ID的域梅垄,如果它是原始類型或者不可變厂捞,那么它需要修改。

所有這些復(fù)雜性真的是有必要嗎队丝?其實(shí)很少靡馁。如果你擴(kuò)展已經(jīng)實(shí)現(xiàn)了Cloneable的類,那么你幾乎沒有選擇机久,只有實(shí)現(xiàn)一個運(yùn)行良好的clone方法臭墨。否則,你通常最好提供對象拷貝的替代方法膘盖。一個更好的對象拷貝方案是胧弛,提供一個拷貝構(gòu)造子(copy constructor)或者拷貝工廠(copy factory)**。一個拷貝構(gòu)造子僅僅是一個接受單個參數(shù)的構(gòu)造子侠畔,這個參數(shù)的類型是包含構(gòu)造子的類结缚,比如,

// 拷貝構(gòu)造子 
public Yum(Yum yum) { ... };

拷貝工廠是一個靜態(tài)工廠(條目1)软棺,類似于拷貝構(gòu)造子:

// 拷貝工廠 
public static Yum newInstance(Yum yum) { ... };

拷貝構(gòu)造子方法和它的靜態(tài)工廠變體相對于Cloneable/clone有許多優(yōu)點(diǎn):他們不依賴于風(fēng)險(xiǎn)大的語言之外的對象創(chuàng)建機(jī)制红竭;它們不要求非強(qiáng)制遵循輕文檔規(guī)范;它們不會與final域的正常使用相沖突喘落;它們不會拋出不必要的受檢查異常茵宪;而且它們不要求強(qiáng)轉(zhuǎn)。

此外瘦棋,拷貝構(gòu)造子或者工廠可以接受一個參數(shù)稀火,它的類型是類實(shí)現(xiàn)的一個接口。比如兽狭,按照慣例憾股,所有通用目的的數(shù)據(jù)集實(shí)現(xiàn)提供了一個構(gòu)造子,它的參數(shù)是Collection或者M(jìn)ap類型箕慧》颍基于接口的拷貝構(gòu)造子和工廠,更恰當(dāng)?shù)亟凶鲛D(zhuǎn)換構(gòu)造子和轉(zhuǎn)換工廠颠焦,讓客戶端選擇拷貝的實(shí)現(xiàn)類型而不是強(qiáng)制客戶端接受原件的實(shí)現(xiàn)類型斩熊。比如,假設(shè)你有一個HashSet伐庭,s粉渠,而且你想拷貝它為一個TreeSet分冈。clone方法不能提供這個特性,但是對于使用轉(zhuǎn)換構(gòu)造子是容易的:new TreeSet<>(s)霸株。

考慮到與Cloneable關(guān)聯(lián)的所有問題雕沉,新接口不應(yīng)該擴(kuò)展它,而且新可擴(kuò)展類不應(yīng)該實(shí)現(xiàn)它去件。對于final類實(shí)現(xiàn)Cloneable坡椒,雖然有更少危害,這應(yīng)該看作一個性能優(yōu)化尤溜,為極少數(shù)適合的情形保留(條目67)倔叼,一般來說,由構(gòu)造子或者工廠提供的拷貝功能是最好的宫莱。這個規(guī)則的一個顯著例外是隊(duì)列丈攒,它最好由clone法拷貝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末授霸,一起剝皮案震驚了整個濱河市巡验,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绝葡,老刑警劉巖深碱,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異藏畅,居然都是意外死亡敷硅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門愉阎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绞蹦,“玉大人,你說我怎么就攤上這事榜旦∮钠撸” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵溅呢,是天一觀的道長澡屡。 經(jīng)常有香客問我,道長咐旧,這世上最難降的妖魔是什么驶鹉? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮铣墨,結(jié)果婚禮上室埋,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好姚淆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布孕蝉。 她就那樣靜靜地躺著,像睡著了一般腌逢。 火紅的嫁衣襯著肌膚如雪降淮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天搏讶,我揣著相機(jī)與錄音骤肛,去河邊找鬼。 笑死窍蓝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的繁成。 我是一名探鬼主播吓笙,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼巾腕!你這毒婦竟也來了面睛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤尊搬,失蹤者是張志新(化名)和其女友劉穎叁鉴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佛寿,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幌墓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了冀泻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片常侣。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弹渔,靈堂內(nèi)的尸體忽然破棺而出胳施,到底是詐尸還是另有隱情,我是刑警寧澤肢专,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布舞肆,位于F島的核電站,受9級特大地震影響博杖,放射性物質(zhì)發(fā)生泄漏椿胯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一欧募、第九天 我趴在偏房一處隱蔽的房頂上張望压状。 院中可真熱鬧,春花似錦、人聲如沸种冬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娱两。三九已至莺匠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間十兢,已是汗流浹背趣竣。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旱物,地道東北人遥缕。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像宵呛,于是被迫代替她去往敵國和親单匣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容