文章作者: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
狈蚤,hashCode
,toString
独泞,clone
和finalize
)都有明確的通用約定歧譬,因?yàn)樵O(shè)計(jì)它們的目的是為了重寫亡笑。任何類都應(yīng)該遵循通用約定重寫這些方法佣盒;不這樣做的話,依賴這些約定的其它類(例如HashMap
和HashSet
)將無(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. Theequals
implementation provided byObject
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 overriddenequals
to check whether twoRandom
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, theequals
implementation inherited fromObject
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, mostSet
implementations inherit theirequals
implementation fromAbstractSet
,List
implementations fromAbstractList
, andMap
implementations fromAbstractMap
.超類已經(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, theequals
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ì)值類而言的。值類僅僅是表示值的類潜叛,例如Integer
或Date
秽褒。程序員用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ì)象同一性,Object
的equals
方法在功能上就如同邏輯等價(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 returntrue
.Symmetric:For any non-null reference values
x
andy
,x.equals(y)
must returntrue
if and only ify.equals(x)
returnstrue
.Transitive:For any non-null reference values
x
,y
,z
,ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
must returntrue
.Consistent: For any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used inequals
comparisons on the objects is modified.For any non-null reference value
x
,x.equals(null)
must returnfalse
.
equals
實(shí)現(xiàn)了一種等價(jià)關(guān)系粪躬。它是:
自反性:對(duì)于任何非空引用值
x
,x.equals(x)
必須返回true
昔穴。對(duì)稱性:對(duì)于任何非空引用值
x
和y
镰官,x.equals(y)
必須返回true
當(dāng)且僅當(dāng)y.equals(x)
返回true
。傳遞性:對(duì)于任何非空引用值吗货,
x
泳唠,y
,z
宙搬,如果x.equals(y)
返回true
并且y.equals(z)
返回true
笨腥,則x.equals(z)
必須返回true
。一致性:對(duì)于任何非空引用值
x
和y
勇垛,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
類使用了基于getClass
的equals
方法怎茫,onUnitCircle
將會(huì)返回false
收壕,無(wú)論CounterPoint
實(shí)例的x
值和y
值是多少。這是因?yàn)榧瞎旄颍?code>onUnitCircle方法中的HashSet
蜜宪,使用equals
方法來(lái)測(cè)試是否包含元素,沒(méi)有CounterPoint
實(shí)例等于Point
祥山。然而圃验,如果你在Point
上使用合適的基于instanceof
的equals
方法,當(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
字段瞻鹏。Timestamp
的equals
實(shí)現(xiàn)確實(shí)違反了對(duì)稱性,如果Timestamp
和Date
用在同一個(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
添加了length
和width
字段躏啰。只要不能直接創(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.URL
的equals
方法依賴于對(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:
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.Use the
instanceof
operator to check if the argument has the correct type. If not, returnfalse
. 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 theequals
contract to permit comparisons across classes that implement the interface. Collection interfaces such asSet
,List
,Map
, andMap.Entry
have this property.Cast the argument to the correct type. Because this cast was preceded by an
instanceof
test, it is guaranteed to succeed.**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, returnfalse
. 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
方法的流程:
使用
==
操作符來(lái)檢查參數(shù)是否是這個(gè)對(duì)象的一個(gè)引用,。如果是,返回true
贺纲。這只是一個(gè)性能優(yōu)化,如果比較的代價(jià)有可能很昂貴脆粥,這樣做是值得的。使用
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
夏哭,List
,Map
和Map.Entry
都有這個(gè)屬性献联。將參數(shù)轉(zhuǎn)換成正確的類型竖配。由于轉(zhuǎn)換測(cè)試已經(jīng)被
instanceof
在之前做了,因此它保證能成功里逆。對(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 equals
method 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ì)于基本類型,如果不是float
或double
谒亦,使用==
操作符進(jìn)行比較竭宰;對(duì)于對(duì)象引用字段,遞歸地調(diào)用equals
方法份招;對(duì)于float
自動(dòng)切揭,使用Float.compare
方法;對(duì)于double
字段锁摔,使用Double.compare
廓旬。float
和double
字段的特別對(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:
如果field
和o.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)闪湾。
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 theequals
method accordingly. Of course yourequals
method also has to satisfy the other two properties (reflexivity and “non-nullity”), but these two usually take care of themselves.當(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 overrideequals
(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, theFile
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 theequals
declaration.It is not uncommon for a programmer to write anequals
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) {
...
}