Effective Java 2.0_中英文對(duì)照_Item 8

文章作者:Tyan
博客:noahsnail.com | CSDN | 簡(jiǎn)書

CHAPTER3 Methods Common to All Objects

ALTHOUGH Object is a concrete class, it is designed primarily for extension. All of its nonfinal methods (equals, hashCode, toString, clone, and finalize) have explicit general contracts because they are designed to be overridden. It is the responsibility of any class overriding these methods to obey their general contracts; failure to do so will prevent other classes that depend on the contracts (such as HashMap and HashSet) from functioning properly in conjunction with the class.

雖然Object是一個(gè)具體的類贩绕,但設(shè)計(jì)它的主要目的是為了擴(kuò)展允悦。它的所有非final方法(equals狈蚤,hashCodetoString独泞,clonefinalize)都有明確的通用約定歧譬,因?yàn)樵O(shè)計(jì)它們的目的是為了重寫亡笑。任何類都應(yīng)該遵循通用約定重寫這些方法佣盒;不這樣做的話,依賴這些約定的其它類(例如HashMapHashSet)將無(wú)法結(jié)合這個(gè)類正確運(yùn)行檐春。

This chapter tells you when and how to override the nonfinal Object methods. The finalize method is omitted from this chapter because it was discussed in Item 7. While not an Object method, Comparable.compareTo is discussed in this chapter because it has a similar character.

會(huì)告本章訴你什么時(shí)候逻淌,怎樣重寫這些非final的Object方法。本章會(huì)忽略finalize方法疟暖,因?yàn)樗贗tem 7中已經(jīng)討論過(guò)了卡儒。雖然不是一個(gè)Object方法,但是這章仍會(huì)討論Comparable.compareTo俐巴,因?yàn)樗幸粋€(gè)類似的特性骨望。

Item 8: Obey the general contract when overriding equals

Overriding the equals method seems simple, but there are many ways to get it wrong, and consequences can be dire. The easiest way to avoid problems is not to override the equals method, in which case each instance of the class is equal only to itself. This is the right thing to do if any of the following conditions apply:

重寫equals方法看似簡(jiǎn)單,但許多方式都會(huì)導(dǎo)致錯(cuò)誤欣舵,結(jié)果是非城骛可怕的。避免這些問(wèn)題的最簡(jiǎn)單方式是不要重寫equals方法缘圈,在這種情況下類的每個(gè)實(shí)例只等價(jià)于它本身劣光。如果符合以下任何條件袜蚕,這樣做就是正確的:

  • Each instance of the class is inherently unique. This is true for classes such as Thread that represent active entities rather than values. The equals implementation provided by Object has exactly the right behavior for these classes.

  • 類的每個(gè)實(shí)例本質(zhì)上都是唯一的。對(duì)于表示活動(dòng)實(shí)體而不是表示值的類確實(shí)如此绢涡,例如Thread牲剃。對(duì)于這些類,Object提供的equals實(shí)現(xiàn)具有完全正確的行為雄可。

  • You don’t care whether the class provides a “l(fā)ogical equality” test. For example, java.util.Random could have overridden equals to check whether two Random instances would produce the same sequence of random numbers going forward, but the designers didn’t think that clients would need or want this functionality. Under these circumstances, the equals implementation inherited from Object is adequate.

  • 不關(guān)心類是否提供“邏輯等價(jià)”的測(cè)試凿傅。例如,java.util.Random可以重寫equals方法來(lái)檢查兩個(gè)Random實(shí)例是否會(huì)產(chǎn)生相同的隨機(jī)數(shù)序列数苫,但設(shè)計(jì)者認(rèn)為客戶不需要或者不想要這個(gè)功能聪舒。在這種情況下,從Object繼承的equals實(shí)現(xiàn)就足夠了虐急。

  • A super class has already overridden equals,and the super class behavior is appropriate for this class. For example, most Set implementations inherit their equals implementation from AbstractSet, List implementations from AbstractList, and Map implementations from AbstractMap.

  • 超類已經(jīng)重寫了equals箱残,超類的行為對(duì)于子類是合適的。例如戏仓,大多數(shù)Set實(shí)現(xiàn)從AbstractSet繼承了equals實(shí)現(xiàn)疚宇,List實(shí)現(xiàn)從AbstractList繼承了equals實(shí)現(xiàn)亡鼠,Map實(shí)現(xiàn)從AbstractMap繼承了equals實(shí)現(xiàn)赏殃。

  • The class is private or package-private,and you are certain that its equals method will never be invoked. Arguably, the equals method should be overridden under these circumstances, in case it is accidentally invoked:

  • 類是私有的或包私有的,可以確定它的equals方法從不會(huì)被調(diào)用间涵。可以說(shuō)仁热,在這些情況下equals方法應(yīng)該重寫,以防它被偶然調(diào)用:

@Override public boolean equals(Object o) {
    throw new AssertionError(); // Method is never called
}

So when is it appropriate to override Object.equals? When a class has a notion of logical equality that differs from mere object identity, and a superclass has not already overridden equals to implement the desired behavior. This is generally the case for value classes. A value class is simply a class that represents a value, such as Integer or Date. A programmer who compares references to value objects using the equals method expects to find out whether they are logically equivalent, not whether they refer to the same object. Not only is overriding the equals method necessary to satisfy programmer expectations; it enables instances to serve as map keys or set elements with predictable, desirable behavior.

什么時(shí)候重寫Object.equals方法是合適的勾哩?如果類具有邏輯等的概念抗蠢,不同于對(duì)象同一性,并且超類沒(méi)有重寫equals方法來(lái)實(shí)現(xiàn)要求的行為思劳,這時(shí)候就需要重寫equals方法迅矛。這種情況通常是對(duì)值類而言的。值類僅僅是表示值的類潜叛,例如IntegerDate秽褒。程序員用equals方法比較值對(duì)象的引用,期望找出它們是否是邏輯等價(jià)的威兜,而不管它們是否是同一對(duì)象销斟。重寫equals方法不僅滿足了程序員的期望;它也能使實(shí)例作為映射表的主鍵或者集合的元素椒舵,使它們表現(xiàn)出可預(yù)期的行為蚂踊。

One kind of value class that does not require the equals method to be overridden is a class that uses instance control (Item 1) to ensure that at most one object exists with each value. Enum types (Item 30) fall into this category. For these classes, logical equality is the same as object identity, so Object’s equals method functions as a logical equals method.

有一種不需要重寫equals方法的值類,它通過(guò)實(shí)例控制(Item 1)來(lái)確保每個(gè)值至多存在一個(gè)對(duì)象笔宿。枚舉類型(Item 30)就是這種類犁钟。對(duì)于這種類而言棱诱,邏輯等價(jià)等同與對(duì)象同一性,Objectequals方法在功能上就如同邏輯等價(jià)方法涝动。

When you override the equals method, you must adhere to its general contract. Here is the contract, copied from the specification for Object [JavaSE6]:

當(dāng)你重寫equals方法時(shí)军俊,你必須遵循通用約定。下面是約定內(nèi)容捧存,從Object規(guī)范[JavaSE6]中拷貝的:

The equals method implements an equivalence relation. It is:

  • Reflexive:For any non-null reference value x,x.equals(x) must return true.

  • Symmetric:For any non-null reference values x and y,x.equals(y) must return true if and only if y.equals(x) returns true.

  • Transitive:For any non-null reference values x,y,z,if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true.

  • Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.

  • For any non-null reference value x,x.equals(null) must return false.

equals實(shí)現(xiàn)了一種等價(jià)關(guān)系粪躬。它是:

  • 自反性:對(duì)于任何非空引用值xx.equals(x)必須返回true昔穴。

  • 對(duì)稱性:對(duì)于任何非空引用值xy镰官,x.equals(y)必須返回true當(dāng)且僅當(dāng)y.equals(x)返回true

  • 傳遞性:對(duì)于任何非空引用值吗货,x泳唠,yz宙搬,如果x.equals(y)返回true并且y.equals(z)返回true笨腥,則x.equals(z)必須返回true

  • 一致性:對(duì)于任何非空引用值xy勇垛,x.equals(y)的多次調(diào)用一致返回true或一致返回false脖母,假設(shè)對(duì)象進(jìn)行equals比較時(shí)沒(méi)有修改任何信息。

  • 對(duì)于非空引用值x闲孤,x.equals(null)必須返回false

Unless you are mathematically inclined, this might look a bit scary, but do not ignore it! If you violate it, you may well find that your program behaves erratically or crashes, and it can be very difficult to pin down the source of the failure. To paraphrase John Donne, no class is an island. Instances of one class are frequently passed to another. Many classes, including all collections classes, depend on the objects passed to them obeying the equals contract.

除非你擅長(zhǎng)數(shù)學(xué)讼积,否則這可能看起來(lái)有點(diǎn)可怕肥照,但不要忽視它!如果你違反了它勤众,你可能會(huì)發(fā)現(xiàn)你的程序表現(xiàn)不正秤咭铮或程序崩潰,并且很難確定失敗的來(lái)源们颜。用John Donne的話來(lái)說(shuō)吕朵,沒(méi)有類是孤立的。一個(gè)類的實(shí)例頻繁傳遞給另一個(gè)類掌桩。許多類边锁,包括所有的集合類,都依賴于傳遞給它們的對(duì)象遵循equals約定波岛。

Now that you are aware of the dangers of violating the equals contract, let’s go over the contract in detail. The good news is that, appearances notwithstanding, the contract really isn’t very complicated. Once you understand it, it’s not hard to adhere to it. Let’s examine the five requirements in turn:

現(xiàn)在你已經(jīng)意識(shí)到了違反了equals約定的危險(xiǎn)茅坛,讓我們?cè)敿?xì)回顧一下這個(gè)約定。好消息是實(shí)際上這個(gè)約定并不復(fù)雜,盡管從表面上來(lái)看不是這樣贡蓖。一旦你理解了它曹鸠,遵循它并不難。讓我們依次檢查這五個(gè)要求:

Reflexivity—The first requirement says merely that an object must be equal to itself. It is hard to imagine violating this requirement unintentionally. If you were to violate it and then add an instance of your class to a collection, the collection’s contains method might well say that the collection didn’t contain the instance that you just added.

自反性——第一個(gè)要求僅僅是說(shuō)一個(gè)對(duì)象必須等價(jià)于它本身斥铺。很難想象會(huì)無(wú)意的違反這個(gè)要求彻桃。如果你違反了它并將你的類實(shí)例添加到一個(gè)集合中,集合的contains方法可能會(huì)說(shuō)這個(gè)集合中不包含你剛剛添加的實(shí)例晾蜘。

Symmetry—The second requirement says that any two objects must agree on whether they are equal. Unlike the first requirement, it’s not hard to imagine violating this one unintentionally. For example, consider the following class, which implements a case-insensitive string. The case of the string is preserved by toString but ignored in comparisons:

對(duì)稱性——第二個(gè)要求是說(shuō)任何兩個(gè)對(duì)象必須對(duì)它們是否相等達(dá)成一致邻眷。不像第一個(gè)要求,不難想象會(huì)無(wú)意的違反這個(gè)要求剔交。例如肆饶,考慮下面的類,它實(shí)現(xiàn)了不區(qū)分大小寫的字符串岖常。字符串保存在toString中驯镊,但在比較時(shí)被忽略了:

// Broken - violates symmetry!
public final class CaseInsensitiveString {
    private final String s;
    public CaseInsensitiveString(String s) {
        if (s == null)
            throw new NullPointerException();
        this.s = s;
    }
    
    // Broken - violates symmetry!
    @Override public boolean equals(Object o) {
        if (o instanceof CaseInsensitiveString)
            return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
            if (o instanceof String)  // One-way interoperability!
                return s.equalsIgnoreCase((String) o);
            return false;
        }
        ...  // Remainder omitted
    }

The well-intentioned equals method in this class naively attempts to interoperate with ordinary strings. Let’s suppose that we have one case-insensitive string and one ordinary one:

這個(gè)類中,equals方法的意圖很好竭鞍,單純的想要與普通的字符串進(jìn)行互操作板惑。假設(shè)我們有一個(gè)區(qū)分大小寫的字符串和一個(gè)普通的字符串:

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";

As expected, cis.equals(s) returns true. The problem is that while the equals method in CaseInsensitiveString knows about ordinary strings, the equals method in String is oblivious to case-insensitive strings. Therefore s.equals(cis) returns false, a clear violation of symmetry. Suppose you put a case-insensitive string into a collection:

正如預(yù)料的那樣,cis.equals(s)返回true偎快。問(wèn)題是雖然CaseInsensitiveString中的equals知道普通的字符串冯乘,但是String中的equals方法不注意不區(qū)分大小寫的字符串。因此s.equals(cis)返回false滨砍,這明顯違反了對(duì)稱性往湿。假設(shè)你將一個(gè)不區(qū)分大小寫的字符串放到一個(gè)集合中:

List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>();
list.add(cis);

What does list.contains(s) return at this point? Who knows? In Sun’s current implementation, it happens to return false, but that’s just an implementation artifact. In another implementation, it could just as easily return true or throw a runtime exception. Once you’ve violated the equals contract, you simply don’t know how other objects will behave when confronted with your object.

這時(shí)list.contains(s)會(huì)返回什么妖异?誰(shuí)知道呢惋戏?在Sun當(dāng)前的實(shí)現(xiàn)中,它碰巧會(huì)返回false他膳,但那僅是一種實(shí)現(xiàn)方案响逢。在另一種實(shí)現(xiàn)中,它也可能很容易的返回true或拋出一個(gè)運(yùn)行時(shí)異常棕孙。一旦你違反了equals約定舔亭,當(dāng)面對(duì)你的對(duì)象時(shí),你根本不指定其它的對(duì)象行為會(huì)怎樣蟀俊。

To eliminate the problem, merely remove the ill-conceived attempt to interoperate with String from the equals method. Once you do this, you can refactor the method to give it a single return:

為了消除這個(gè)問(wèn)題钦铺,只要從equals方法中移除與String進(jìn)行交互的,考慮不周的嘗試即可肢预。一旦你這樣做了矛洞,你可以重構(gòu)這個(gè)方法給它一個(gè)返回即可:

 @Override 
 public boolean equals(Object o) {
    return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}

Transitivity—The third requirement of the equals contract says that if one object is equal to a second and the second object is equal to a third, then the first object must be equal to the third. Again, it’s not hard to imagine violating this requirement unintentionally. Consider the case of a subclass that adds a new value component to its superclass. In other words, the subclass adds a piece of information that affects equals comparisons. Let’s start with a simple immutable two-dimensional integer point class:

傳遞性——equals約定的第三個(gè)要求是說(shuō)如果一個(gè)對(duì)象等價(jià)于第二個(gè)對(duì)象,而第二個(gè)對(duì)象等價(jià)于第三個(gè)對(duì)象烫映,則第一個(gè)對(duì)象等價(jià)于第三個(gè)對(duì)象沼本。同樣的噩峦,不難想象會(huì)無(wú)意中違反這個(gè)要求〕檎祝考慮這樣一種情況识补,子類添加一個(gè)新的值組件到它的超類中。換句話說(shuō)辫红,子類添加的信息會(huì)影響equals比較凭涂。以一個(gè)簡(jiǎn)單的不可變的二維整數(shù)點(diǎn)類作為開始:

public class Point {
    private final int x;
    private final int y;
    public Point(int x, int y) {
        this.x = x;
        this.y = y; 
    }

    @Override 
    public boolean equals(Object o) {
        if (!(o instanceof Point))
            return false;
        Point p = (Point)o;
        return p.x == x && p.y == y;
    }
    ...  // Remainder omitted
}

Suppose you want to extend this class, adding the notion of color to a point:

假設(shè)你想擴(kuò)展這個(gè)類,給點(diǎn)添加顏色的概念:

public class ColorPoint extends Point {
    private final Color color;
    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }
    ...  // Remainder omitted
}

How should the equals method look? If you leave it out entirely, the implementation is inherited from Point and color information is ignored in equals comparisons. While this does not violate the equals contract, it is clearly unacceptable. Suppose you write an equals method that returns true only if its argument is another color point with the same position and color:

equals方法應(yīng)該看起來(lái)是怎樣的贴妻?如果一點(diǎn)也不修改导盅,直接從Point繼承equals方法,在進(jìn)行equals比較時(shí)顏色信息會(huì)被忽略揍瑟。雖然這沒(méi)有違反equals約定白翻,但很明顯這是不可接受的。假設(shè)你寫了一個(gè)equals方法绢片,只有在它的參數(shù)是另一個(gè)有色點(diǎn)滤馍,且它們具有相同的位置和顏色時(shí)才返回true

// Broken - violates symmetry!
@Override 
public boolean equals(Object o) {
    if (!(o instanceof ColorPoint))
        return false;
    return super.equals(o) && ((ColorPoint) o).color == color;
}

The problem with this method is that you might get different results when comparing a point to a color point and vice versa. The former comparison ignores color, while the latter comparison always returns false because the type of the argument is incorrect. To make this concrete, let’s create one point and one color point:

這個(gè)方法的問(wèn)題在于:當(dāng)你比較一個(gè)普通點(diǎn)和一個(gè)有色點(diǎn)或相反的情況時(shí),你可能會(huì)得到不同的結(jié)果底循。前者的比較忽略了顏色巢株,而后者總是返回false,因?yàn)閰?shù)類型不正確熙涤。為了使這個(gè)更具體一點(diǎn)阁苞,我們創(chuàng)建一個(gè)普通點(diǎn)和一個(gè)有色點(diǎn):

Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);

Then p.equals(cp) returns true, while cp.equals(p) returns false. You might try to fix the problem by having ColorPoint.equals ignore color when doing “mixed comparisons”:

p.equals(cp)返回true,而cp.equals(p)返回false祠挫。你可能想讓ColorPoint.equals進(jìn)行比較混合比較時(shí)忽略顏色來(lái)修正這個(gè)問(wèn)題:

// Broken - violates transitivity!
@Override 
public boolean equals(Object o) {
    if (!(o instanceof Point))
        return false;
    // If o is a normal Point, do a color-blind comparison
    if (!(o instanceof ColorPoint))
        return o.equals(this);
    // o is a ColorPoint; do a full comparison
    return super.equals(o) && ((ColorPoint)o).color == color;
}

This approach does provide symmetry, but at the expense of transitivity:

這個(gè)方法提供了對(duì)稱性那槽,但違反了傳遞性:

ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);

Now p1.equals(p2) and p2.equals(p3) return true, while p1.equals(p3) returns false, a clear violation of transitivity. The first two comparisons are “color-blind,” while the third takes color into account.

現(xiàn)在p1.equals(p2)p2.equals(p3)返回true,而p1.equals(p3)返回false等舔,很明顯這違反了傳遞性骚灸。前兩個(gè)比較忽略了顏色,而第三個(gè)比較考慮了顏色慌植。

So what’s the solution? It turns out that this is a fundamental problem of equivalence relations in object-oriented languages. There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you are willing to forgo the benefits of object-oriented abstraction.

因此解決方案是什么甚牲?事實(shí)證明:在面向?qū)ο笳Z(yǔ)言中,等價(jià)關(guān)系問(wèn)題是一個(gè)基本的問(wèn)題蝶柿。當(dāng)保留equals約定時(shí)丈钙,你無(wú)法在擴(kuò)展一個(gè)實(shí)例化的類的同時(shí)添加值組件,除非你愿意放棄面向?qū)ο蟪橄蟮膬?yōu)勢(shì)交汤。

You may hear it said that you can extend an instantiable class and add a value component while preserving the equals contract by using a getClass test in place of the instanceof test in the equals method:

你可能聽說(shuō)過(guò)你可以在equals方法中通過(guò)使用getClass測(cè)試代替instanceof測(cè)試雏赦,從而在擴(kuò)展一個(gè)可實(shí)例化的類并添加值組件的同時(shí),保留equals約定:

// Broken - violates Liskov substitution principle (page 40)
@Override 
public boolean equals(Object o) {
    if (o == null || o.getClass() != getClass())
        return false;
    Point p = (Point) o;
    return p.x == x && p.y == y;
}

This has the effect of equating objects only if they have the same implementation class. While this may not seem so bad, the consequences are unacceptable.

當(dāng)且僅當(dāng)它們具有相同的實(shí)現(xiàn)類時(shí),上面的代碼在比較對(duì)象時(shí)才會(huì)有效喉誊。雖然這不是很糟糕邀摆,但結(jié)果是不可接受的。

Let’s suppose we want to write a method to tell whether an integer point is on the unit circle. Here is one way we could do it:

假設(shè)我們想寫一個(gè)方法來(lái)判斷一個(gè)整數(shù)點(diǎn)是否在單位圓上伍茄。下面是一種寫法:

// Initialize UnitCircle to contain all Points on the unit circle private static final Set<Point> unitCircle;
static {
    unitCircle = new HashSet<Point>();
    unitCircle.add(new Point( 1,  0));
    unitCircle.add(new Point( 0,  1));
    unitCircle.add(new Point(-1,  0));
    unitCircle.add(new Point( 0, -1));
}
public static boolean onUnitCircle(Point p) {
    return unitCircle.contains(p);
}

While this may not be the fastest way to implement the functionality, it works fine. But suppose you extend Point in some trivial way that doesn’t add a value component, say, by having its constructor keep track of how many instances have been created:

雖然這可能不是實(shí)現(xiàn)這個(gè)功能的最快方式栋盹,但它確實(shí)有效。但假設(shè)你以某種不添加值組件的方式擴(kuò)展了Point敷矫,例如通過(guò)它的構(gòu)造函數(shù)來(lái)追蹤創(chuàng)建了多少實(shí)例:

public class CounterPoint extends Point {
    private static final AtomicInteger counter = new AtomicInteger();

    public CounterPoint(int x, int y) {
        super(x, y);
        counter.incrementAndGet();
    }
    
    public int numberCreated() { 
        return counter.get(); 
    }
}

The Liskov substitution principle says that any important property of a type should also hold for its subtypes, so that any method written for the type should work equally well on its subtypes [Liskov87]. But suppose we pass a CounterPoint instance to the onUnitCircle method. If the Point class uses a getClass based equals method, the onUnitCircle method will return false regardless of the CounterPoint instance’s x and y values. This is so because collections, such as the HashSet例获,used by the onUnitCircle method, use the equals method to test for containment, and no CounterPoint instance is equal to any Point. If, however, you use a proper instanceof-based equals method on Point, the same onUnitCircle method will work fine when presented with a CounterPoint.

里氏替換原則認(rèn)為,一個(gè)類型的任何重要屬性也適用于它的子類型曹仗,因此該類型編寫的任何方法在它的子類型中也都應(yīng)該工作良好[Liskov87]榨汤。但假設(shè)我們給onUnitCircle傳遞了一個(gè)CounterPoint實(shí)例。如果Point類使用了基于getClassequals方法怎茫,onUnitCircle將會(huì)返回false收壕,無(wú)論CounterPoint實(shí)例的x值和y值是多少。這是因?yàn)榧瞎旄颍?code>onUnitCircle方法中的HashSet蜜宪,使用equals方法來(lái)測(cè)試是否包含元素,沒(méi)有CounterPoint實(shí)例等于Point祥山。然而圃验,如果你在Point上使用合適的基于instanceofequals方法,當(dāng)面對(duì)CounterPoint時(shí)缝呕,同樣的onUnitCircle方法會(huì)工作的很好澳窑。

While there is no satisfactory way to extend an instantiable class and add a value component, there is a fine workaround. Follow the advice of Item 16, “Favor composition over inheritance.” Instead of having ColorPoint extend Point, give ColorPoint a private Point field and a public view method (Item 5) that returns the point at the same position as this color point:

盡管沒(méi)有令人滿意的方式來(lái)擴(kuò)展一個(gè)可實(shí)例化的類并添加值組件,但有一個(gè)很好的解決方案供常。遵循Item 16 “Favor composition over inheritance”的建議摊聋,不再讓ColorPoint繼承Point,而是通過(guò)在ColorPoint中添加一個(gè)私有的Point字段和一個(gè)公有的視圖方法(Item 5)话侧,此方法返回一個(gè)與有色點(diǎn)具有相同位置的普通點(diǎn):

// Adds a value component without violating the equals contract
public class ColorPoint {
    private final Point point;
    private final Color color;
    public ColorPoint(int x, int y, Color color) {
        if (color == null)
            throw new NullPointerException();
        point = new Point(x, y);
        this.color = color;
    }

    /**
     * Returns the point-view of this color point.
     */
    public Point asPoint() { 
        return point;
    }
    @Override 
    public boolean equals(Object o) {
        if (!(o instanceof ColorPoint))
            return false;
        ColorPoint cp = (ColorPoint) o;
        return cp.point.equals(point) && cp.color.equals(color);
    }
    ...  // Remainder omitted
}

There are some classes in the Java platform libraries that do extend an instantiable class and add a value component. For example, java.sql.Timestamp extends java.util.Date and adds a nanoseconds field. The equals implementation for Timestamp does violate symmetry and can cause erratic behavior if Timestamp and Date objects are used in the same collection or are otherwise intermixed. The Timestamp class has a disclaimer cautioning programmers against mixing dates and timestamps. While you won’t get into trouble as long as you keep them separate, there’s nothing to prevent you from mixing them, and the resulting errors can be hard to debug. This behavior of the Timestamp class was a mistake and should not be emulated.

在Java平臺(tái)庫(kù)中有一些類擴(kuò)展了一個(gè)可實(shí)例化的類并添加了一個(gè)值組件栗精。例如,java.sql.Timestamp擴(kuò)展了java.util.Date并添加了一個(gè)nanoseconds字段瞻鹏。Timestampequals實(shí)現(xiàn)確實(shí)違反了對(duì)稱性,如果TimestampDate用在同一個(gè)集合中或混雜在一起鹿寨,會(huì)引起不穩(wěn)定的行為新博。Timestamp類有一個(gè)免責(zé)聲明,警告程序員不要混合日期和時(shí)間戳脚草。雖然只要你將它們分開就不會(huì)有麻煩赫悄,但是沒(méi)有任何東西阻止你混合它們,而且產(chǎn)生的錯(cuò)誤很難調(diào)試。Timestamp類的這個(gè)行為是一個(gè)錯(cuò)誤埂淮,不應(yīng)該進(jìn)行模仿姑隅。

Note that you can add a value component to a subclass of an abstract class without violating the equals contract. This is important for the sort of class hierarchies that you get by following the advice in Item 20, “Prefer class hierarchies to tagged classes.” For example, you could have an abstract class Shape with no value components, a subclass Circle that adds a radius field, and a subclass Rectangle that adds length and width fields. Problems of the sort shown above won’t occur so long as it is impossible to create a superclass instance directly.

注意,你可以添加值組件到抽象類的子類而且不會(huì)違反equals約定倔撞。對(duì)于遵循Item 20 “Prefer class hierarchies to tagged classes”的建議而得到這種類層次來(lái)說(shuō)讲仰,這是非常重要的。例如痪蝇,你可以有一個(gè)沒(méi)有值組件的抽象類Shape鄙陡,子類Circle添加了radius字段,子類Rectangle添加了lengthwidth字段躏啰。只要不能直接創(chuàng)建一個(gè)超類實(shí)例趁矾,上面的種種問(wèn)題就不會(huì)發(fā)生。

Consistency—The fourth requirement of the equals contract says that if two objects are equal, they must remain equal for all time unless one (or both) of them is modified. In other words, mutable objects can be equal to different objects at different times while immutable objects can’t. When you write a class, think hard about whether it should be immutable (Item 15). If you conclude that it should, make sure that your equals method enforces the restriction that equal objects remain equal and unequal objects remain unequal for all time.

一致性——equals約定的第四個(gè)要求是說(shuō)如果兩個(gè)對(duì)象相等给僵,它們必須一致相等毫捣,除非其中一個(gè)(或二者)被修改了。換句話說(shuō)帝际,可變對(duì)象在不同的時(shí)間可以等于不同的對(duì)象而不可變對(duì)象不能培漏。當(dāng)你寫了一個(gè)類,仔細(xì)想想它是否應(yīng)該是不可變的(Item 15)胡本。如果你推斷它應(yīng)該是不可變的牌柄,那么要確保你的equals方法滿足這樣的約束條件:相等的對(duì)象永遠(yuǎn)相等,不等的對(duì)象永遠(yuǎn)不等侧甫。

Whether or not a class is immutable, do not write an equals method that depends on unreliable resources. It’s extremely difficult to satisfy the consistency requirement if you violate this prohibition. For example, java.net.URL’s equals method relies on comparison of the IP addresses of the hosts associated with the URLs. Translating a host name to an IP address can require network access, and it isn’t guaranteed to yield the same results over time. This can cause the URL equals method to violate the equals contract and has caused problems in practice. (Unfortunately, this behavior cannot be changed due to compatibility requirements.) With very few exceptions, equals methods should perform deterministic computations on memory-resident objects.

無(wú)論一個(gè)類是否是不可變的珊佣,都不要寫一個(gè)依賴于不可靠資源的equals方法。如果你違反了這個(gè)禁令披粟,要滿足一致性要求是非常困難的咒锻。例如,java.net.URLequals方法依賴于對(duì)關(guān)聯(lián)URL主機(jī)的IP地址的比較守屉。將主機(jī)名轉(zhuǎn)換成IP地址可能需要訪問(wèn)網(wǎng)絡(luò)惑艇,隨時(shí)間推移它不能保證取得相同的結(jié)果。這可能會(huì)導(dǎo)致URL equals方法違反equals約定并在實(shí)踐中產(chǎn)生問(wèn)題拇泛。(很遺憾滨巴,由于兼容性問(wèn)題,這一行為不能被修改俺叭。)除了極少數(shù)例外恭取,equals方法應(yīng)該對(duì)常駐內(nèi)存對(duì)象進(jìn)行確定性計(jì)算。

Non-nullity”—The final requirement, which in the absence of a name I have taken the liberty of calling “non-nullity,” says that all objects must be unequal to null. While it is hard to imagine accidentally returning true in response to the invocation o.equals(null), it isn’t hard to imagine accidentally throwing a NullPointerException. The general contract does not allow this. Many classes
have equals methods that guard against this with an explicit test for null:

非空性”——最后的要求由于沒(méi)有名字我稱之為“非空性”熄守,這個(gè)要求是說(shuō)所有的對(duì)象都不等于null蜈垮。雖然很難想象調(diào)用o.equals(null)會(huì)偶然的返回true耗跛,但不難想象會(huì)意外拋出NullPointerException的情況。通用約定不允許出現(xiàn)這種情況攒发。許多類的equals方法為了防止出現(xiàn)這種情況都進(jìn)行對(duì)null的顯式測(cè)試:

@Override 
public boolean equals(Object o) {
    if (o == null)
        return false;
    ...
}

This test is unnecessary. To test its argument for equality, the equals method must first cast its argument to an appropriate type so its accessors may be invoked or its fields accessed. Before doing the cast, the method must use the instanceof operator to check that its argument is of the correct type:

這個(gè)測(cè)試是沒(méi)必要的调塌。為了平等測(cè)試其參數(shù),為了調(diào)用它的訪問(wèn)器或訪問(wèn)其字段惠猿,equals方法首先必須將它的參數(shù)轉(zhuǎn)換成合適的類型羔砾。在進(jìn)行轉(zhuǎn)換之前,equals方法必須使用instanceof操作符來(lái)檢查它的參數(shù)是否是正確的類型:

@Override 
public boolean equals(Object o) {
    if (!(o instanceof MyType))
        return false;
    MyType mt = (MyType) o;
    ...
}

If this type check were missing and the equals method were passed an argument of the wrong type, the equals method would throw a ClassCastException, which violates the equals contract. But the instanceof operator is specified to return false if its first operand is null, regardless of what type appears in the second operand [JLS, 15.20.2]. Therefore the type check will return false if null is passed in, so you don’t need a separate null check.

如果缺少類型檢查紊扬,equals方法傳入了一個(gè)錯(cuò)誤類型的參數(shù)蜒茄,equals方法會(huì)拋出ClassCastException,這違反了equals約定餐屎。但當(dāng)指定instanceof時(shí)檀葛,如果它的第一個(gè)操作數(shù)為null,無(wú)論它的第二個(gè)操作數(shù)是什么類型腹缩,它都會(huì)返回false[JLS, 15.20.2]屿聋。所以如果傳入null類型檢查將會(huì)返回false,因此你不必進(jìn)行單獨(dú)的null檢查藏鹊。

Putting it all together, here’s a recipe for a high-quality equals method:

  1. Use the == operator to check if the argument is a reference to this object. If so, return true. This is just a performance optimization, but one that is worth doing if the comparison is potentially expensive.

  2. Use the instanceof operator to check if the argument has the correct type. If not, return false. Typically, the correct type is the class in which the method occurs. Occasionally, it is some interface implemented by this class. Use an interface if the class implements an interface that refines the equals contract to permit comparisons across classes that implement the interface. Collection interfaces such as Set, List, Map, and Map.Entry have this property.

  3. Cast the argument to the correct type. Because this cast was preceded by an instanceof test, it is guaranteed to succeed.

  4. **For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object. **If all these tests succeed, return true; otherwise, return false. If the type in step 2 is an interface, you must access the argument’s fields via interface methods; if the type is a class, you may be able to access the fields directly, depending on their accessibility.

將上面所有的內(nèi)容放在一起润讥,下面是編寫一個(gè)高質(zhì)量equals方法的流程:

  1. 使用==操作符來(lái)檢查參數(shù)是否是這個(gè)對(duì)象的一個(gè)引用,。如果是,返回true贺纲。這只是一個(gè)性能優(yōu)化,如果比較的代價(jià)有可能很昂貴脆粥,這樣做是值得的。

  2. 使用instanceof操作符來(lái)檢查參數(shù)類型是否正確影涉。如果不正確变隔,返回false。通常蟹倾,正確的類型是指equals方法所在的那個(gè)類匣缘。有時(shí)候,它是這個(gè)類實(shí)現(xiàn)的一些接口鲜棠。如果一個(gè)類實(shí)現(xiàn)了一個(gè)接口肌厨,這個(gè)接口提煉了equals約定來(lái)允許比較那些實(shí)現(xiàn)了這個(gè)接口類,那么就使用接口岔留。集合接口例如Set夏哭,ListMapMap.Entry都有這個(gè)屬性献联。

  3. 將參數(shù)轉(zhuǎn)換成正確的類型竖配。由于轉(zhuǎn)換測(cè)試已經(jīng)被instanceof在之前做了,因此它保證能成功里逆。

  4. 對(duì)于類中的每一個(gè)“有效”字段进胯,檢查參數(shù)的這個(gè)字段是否匹配這個(gè)對(duì)象的對(duì)應(yīng)字段。如果所有的這些測(cè)試都成功了原押,返回true胁镐;否則返回false。如果第二步中的類型是一個(gè)接口诸衔,你必須通過(guò)接口方法訪問(wèn)參數(shù)的字段盯漂;如果類型是一個(gè)類,你可能要直接訪問(wèn)字段笨农,依賴于它們的可訪問(wèn)性就缆。

For primitive fields whose type is not float or double, use the == operator for comparisons; for object reference fields, invoke the equalsmethod recursively; for float fields, use the Float.compare method; and for double fields, use Double.compare. The special treatment of float and double fields is made necessary by the existence of Float.NaN, -0.0f and the analogous double constants; see the Float.equals documentation for details. For array fields, apply these guidelines to each element. If every element in an array field is significant, you can use one of the Arrays.equals methods added in release 1.5.

對(duì)于基本類型,如果不是floatdouble谒亦,使用==操作符進(jìn)行比較竭宰;對(duì)于對(duì)象引用字段,遞歸地調(diào)用equals方法份招;對(duì)于float自動(dòng)切揭,使用Float.compare方法;對(duì)于double字段锁摔,使用Double.compare廓旬。floatdouble字段的特別對(duì)待是有必要的,因?yàn)榇嬖?code>Float.NaN谐腰,-0.0f和類似的double常量孕豹;更多細(xì)節(jié)請(qǐng)看Float.equals。對(duì)于數(shù)組字段怔蚌,對(duì)每個(gè)元素應(yīng)用這些指導(dǎo)巩步。如果數(shù)組中的每個(gè)元素都是有意義的,你可以使用1.5版本中添加的Arrays.equals方法桦踊。

Some object reference fields may legitimately contain null. To avoid the possibility of a NullPointerException, use this idiom to compare such fields:

某些對(duì)象引用字段可能合理的包含null椅野。為了避免產(chǎn)生NullPointerException的可能性,使用下面的習(xí)慣用法來(lái)比較這些字段:

(field == null ? o.field == null : field.equals(o.field))

This alternative may be faster if field and o.field are often identical:

如果fieldo.field經(jīng)常是等價(jià)的籍胯,使用下面的可替代方式可能會(huì)更快:

(field == o.field || (field != null && field.equals(o.field)))

For some classes, such as CaseInsensitiveString above, field comparisons are more complex than simple equality tests. If this is the case, you may want to store a canonical form of the field, so the equals method can do cheap exact comparisons on these canonical forms rather than more costly inexact comparisons. This technique is most appropriate for immutable classes (Item 15); if the object can change, you must keep the canonical form up to date.

對(duì)于某些類而言竟闪,例如上面的CaseInsensitiveString,字段比較比簡(jiǎn)單的相等性檢測(cè)更復(fù)雜杖狼。如果是這種情況炼蛤,你可能想存儲(chǔ)這個(gè)字段的標(biāo)準(zhǔn)形式,因此equals方法可以在這些標(biāo)準(zhǔn)形式上進(jìn)行低開銷的精確比較蝶涩,而不是更高代碼的非精確比較理朋。這種技術(shù)最適合不可變類(Item 15)絮识;如果對(duì)象可以改變,你必須保持最新的標(biāo)準(zhǔn)形式嗽上。

The performance of the equals method may be affected by the order in which fields are compared. For best performance, you should first compare fields that are more likely to differ, less expensive to compare, or, ideally, both. You must not compare fields that are not part of an object’s logical state, such as Lock fields used to synchronize operations. You need not compare redundant fields, which can be calculated from “significant fields,” but doing so may improve the performance of the equals method. If a redundant field amounts to a summary description of the entire object, comparing this field will save you the expense of comparing the actual data if the comparison fails. For example, suppose you have a Polygon class, and you cache the area. If two polygons have unequal areas, you needn’t bother comparing their edges and vertices.

equals方法的性能可能會(huì)受到字段比較順序的影響次舌。為了最佳性能,你首先應(yīng)該比較那些更可能不同兽愤,比較代價(jià)更小的字段彼念,或者理想情況下二者兼具的字段。你不能比較那些不屬于對(duì)象邏輯狀態(tài)一部分的字段浅萧,例如同步操作中的Lock字段逐沙。你也不需要比較冗余的字段,它們能從“有意義字段”中計(jì)算出來(lái)洼畅,但這樣做可能會(huì)改善equals方法的性能吩案。如果冗余字段相當(dāng)于整個(gè)對(duì)象的概要描述,比較這個(gè)字段土思,如果失敗的話會(huì)節(jié)省你比較真正數(shù)據(jù)的開銷务热。例如,假設(shè)你有一個(gè)Polygon類己儒,并且你緩存這個(gè)區(qū)域崎岂。如果兩個(gè)多邊形有不同的面積,你就不需要比較它們的邊和頂點(diǎn)闪湾。

  1. When you are finished writing your equals method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent? And don’t just ask yourself; write unit tests to check that these properties hold! If they don’t, figure out why not, and modify the equals method accordingly. Of course your equals method also has to satisfy the other two properties (reflexivity and “non-nullity”), but these two usually take care of themselves.

  2. 當(dāng)你完成了equals方法的編寫時(shí)冲甘,問(wèn)你自己三個(gè)問(wèn)題:它是否是對(duì)稱的?是否是可傳遞的途样?是否是一致的江醇?并且不要只問(wèn)你自己;編寫單元測(cè)試來(lái)檢查是否擁有這些屬性何暇!如果沒(méi)有這些屬性陶夜,弄清楚為什么沒(méi)有,對(duì)應(yīng)的修改equals方法裆站。當(dāng)然你的equals方法也必須滿足其它兩個(gè)屬性(自反性和“非空性”)条辟,但這兩個(gè)屬性通常會(huì)自動(dòng)滿足。

For a concrete example of an equals method constructed according to the above recipe, see PhoneNumber.equals in Item 9. Here are a few final caveats:

根據(jù)上述規(guī)則構(gòu)建的equals方法具體例子請(qǐng)看Item 9的PhoneNumber.equals`宏胯。下面是一些最后的警告:

  • Always override hashCode when you override equals(Item9).

  • 當(dāng)你重寫equals時(shí)羽嫡,總是重寫hashCode方法(Item9)

  • Don’t try to be too clever. If you simply test fields for equality, it’s not hard to adhere to the equals contract. If you are overly aggressive in searching for equivalence, it’s easy to get into trouble. It is generally a bad idea to take any form of aliasing into account. For example, the File class shouldn’t attempt to equate symbolic links referring to the same file. Thankfully, it doesn’t.

  • 不要試圖自作聰明肩袍。如果你簡(jiǎn)單的測(cè)試字段的相等性杭棵,不難遵循equals約定。如果過(guò)度的追求等價(jià)關(guān)系氛赐,很容易陷入到麻煩中魂爪∠认希考慮任何形式的別名通常不是一個(gè)好想法。例如甫窟,File類不應(yīng)該試圖把指向同名的符號(hào)鏈接看作相等密浑。所幸它沒(méi)有這樣做蛙婴。

  • Don’t substitute another type for Object in the equals declaration.It is not uncommon for a programmer to write an equals method that looks like this, and then spend hours puzzling over why it doesn’t work properly:

  • 不要將equals聲明中的Object對(duì)象替換為其它對(duì)象粗井。對(duì)于程序員來(lái)講,寫一個(gè)equals方法看起來(lái)像下面的一樣是不常見的街图,并且花費(fèi)了好幾個(gè)小時(shí)都不明白它為什么不能正確工作:

public boolean equals(MyClass o) {
    ...
}

The problem is that this method does not override Object.equals, whose argument is of type Object, but overloads it instead (Item 41). It is acceptable to provide such a “strongly typed” equals method in addition to the normal one as long as the two methods return the same result, but there is no compelling reason to do so. It may provide minor performance gains under certain circumstances, but it isn’t worth the added complexity (Item 55).

這個(gè)問(wèn)題在于這個(gè)方法沒(méi)有重寫Object.equals方法浇衬,Object.equals方法的參數(shù)類型是Object,但相反餐济,它重載了equals方法(Item 41)耘擂。除了正常的equals方法之外,提供這樣一個(gè)“強(qiáng)類型”equals方法是可接受的絮姆,只要這兩個(gè)方法返回同樣的結(jié)果醉冤,但沒(méi)有令人信服的理由去這樣做。在某些特定環(huán)境下它可能會(huì)提供很小的收益篙悯,但相對(duì)于增加的復(fù)雜性來(lái)講是不值得的(Item 55)蚁阳。

Consistent use of the @Override annotation, as illustrated throughout this item, will prevent you from making this mistake (Item 36). This equals method won’t compile and the error message will tell you exactly what is wrong:

正如本條目闡述的那樣,@Override注解的一致使用會(huì)阻止你犯這個(gè)錯(cuò)誤(Item 36)鸽照。這個(gè)equals方法不能編譯并且錯(cuò)誤信息會(huì)確切告訴你錯(cuò)誤是什么螺捐。

@Override 
public boolean equals(MyClass o) {
    ...
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市矮燎,隨后出現(xiàn)的幾起案子定血,更是在濱河造成了極大的恐慌,老刑警劉巖诞外,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜沟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡峡谊,警方通過(guò)查閱死者的電腦和手機(jī)茫虽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)靖苇,“玉大人席噩,你說(shuō)我怎么就攤上這事∠捅冢” “怎么了悼枢?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)脾拆。 經(jīng)常有香客問(wèn)我馒索,道長(zhǎng)莹妒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任绰上,我火速辦了婚禮旨怠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜈块。我一直安慰自己鉴腻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布百揭。 她就那樣靜靜地躺著爽哎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪器一。 梳的紋絲不亂的頭發(fā)上课锌,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音祈秕,去河邊找鬼渺贤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛请毛,可吹牛的內(nèi)容都是我干的志鞍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼获印,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼述雾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起兼丰,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤玻孟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鳍征,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黍翎,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年艳丛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匣掸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氮双,死狀恐怖碰酝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情戴差,我是刑警寧澤送爸,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響袭厂,放射性物質(zhì)發(fā)生泄漏墨吓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一纹磺、第九天 我趴在偏房一處隱蔽的房頂上張望帖烘。 院中可真熱鬧,春花似錦橄杨、人聲如沸秘症。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)历极。三九已至,卻和暖如春衷佃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹄葱。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工氏义, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人图云。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓惯悠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親竣况。 傳聞我的和親對(duì)象是個(gè)殘疾皇子克婶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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