文章作者:Tyan
博客:noahsnail.com | CSDN | 简书
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
是一个具体的类,但设计它的主要目的是为了扩展。它的所有非final
方法(equals
,hashCode
,toString
,clone
和finalize
)都有明确的通用约定,因为设计它们的目的是为了重写。任何类都应该遵循通用约定重写这些方法;不这样做的话,依赖这些约定的其它类(例如HashMap
和HashSet
)将无法结合这个类正确运行。
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.
会告本章诉你什么时候,怎样重写这些非final的Object
方法。本章会忽略finalize
方法,因为它在Item 7中已经讨论过了。虽然不是一个Object
方法,但是这章仍会讨论Comparable.compareTo
,因为它有一个类似的特性。
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
方法看似简单,但许多方式都会导致错误,结果是非常可怕的。避免这些问题的最简单方式是不要重写equals
方法,在这种情况下类的每个实例只等价于它本身。如果符合以下任何条件,这样做就是正确的:
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.类的每个实例本质上都是唯一的。对于表示活动实体而不是表示值的类确实如此,例如
Thread
。对于这些类,Object
提供的equals
实现具有完全正确的行为。You don’t care whether the class provides a “logical 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.不关心类是否提供“逻辑等价”的测试。例如,
java.util.Random
可以重写equals
方法来检查两个Random
实例是否会产生相同的随机数序列,但设计者认为客户不需要或者不想要这个功能。在这种情况下,从Object
继承的equals
实现就足够了。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
.超类已经重写了
equals
,超类的行为对于子类是合适的。例如,大多数Set
实现从AbstractSet
继承了equals
实现,List
实现从AbstractList
继承了equals
实现,Map
实现从AbstractMap
继承了equals
实现。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
方法从不会被调用。可以说,在这些情况下equals
方法应该重写,以防它被偶然调用:
@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.
什么时候重写Object.equals
方法是合适的?如果类具有逻辑等的概念,不同于对象同一性,并且超类没有重写equals
方法来实现要求的行为,这时候就需要重写equals
方法。这种情况通常是对值类而言的。值类仅仅是表示值的类,例如Integer
或Date
。程序员用equals
方法比较值对象的引用,期望找出它们是否是逻辑等价的,而不管它们是否是同一对象。重写equals
方法不仅满足了程序员的期望;它也能使实例作为映射表的主键或者集合的元素,使它们表现出可预期的行为。
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
方法的值类,它通过实例控制(Item 1)来确保每个值至多存在一个对象。枚举类型(Item 30)就是这种类。对于这种类而言,逻辑等价等同与对象同一性,Object
的equals
方法在功能上就如同逻辑等价方法。
When you override the equals
method, you must adhere to its general contract. Here is the contract, copied from the specification for Object
[JavaSE6]:
当你重写equals
方法时,你必须遵循通用约定。下面是约定内容,从Object
规范[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
实现了一种等价关系。它是:
自反性:对于任何非空引用值
x
,x.equals(x)
必须返回true
。对称性:对于任何非空引用值
x
和y
,x.equals(y)
必须返回true
当且仅当y.equals(x)
返回true
。传递性:对于任何非空引用值,
x
,y
,z
,如果x.equals(y)
返回true
并且y.equals(z)
返回true
,则x.equals(z)
必须返回true
。一致性:对于任何非空引用值
x
和y
,x.equals(y)
的多次调用一致返回true
或一致返回false
,假设对象进行equals
比较时没有修改任何信息。对于非空引用值
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.
除非你擅长数学,否则这可能看起来有点可怕,但不要忽视它!如果你违反了它,你可能会发现你的程序表现不正常或程序崩溃,并且很难确定失败的来源。用John Donne的话来说,没有类是孤立的。一个类的实例频繁传递给另一个类。许多类,包括所有的集合类,都依赖于传递给它们的对象遵循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:
现在你已经意识到了违反了equals
约定的危险,让我们详细回顾一下这个约定。好消息是实际上这个约定并不复杂,尽管从表面上来看不是这样。一旦你理解了它,遵循它并不难。让我们依次检查这五个要求:
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.
自反性——第一个要求仅仅是说一个对象必须等价于它本身。很难想象会无意的违反这个要求。如果你违反了它并将你的类实例添加到一个集合中,集合的contains
方法可能会说这个集合中不包含你刚刚添加的实例。
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:
对称性——第二个要求是说任何两个对象必须对它们是否相等达成一致。不像第一个要求,不难想象会无意的违反这个要求。例如,考虑下面的类,它实现了不区分大小写的字符串。字符串保存在toString
中,但在比较时被忽略了:
// 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:
这个类中,equals
方法的意图很好,单纯的想要与普通的字符串进行互操作。假设我们有一个区分大小写的字符串和一个普通的字符串:
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:
正如预料的那样,cis.equals(s)
返回true
。问题是虽然CaseInsensitiveString
中的equals
知道普通的字符串,但是String
中的equals
方法不注意不区分大小写的字符串。因此s.equals(cis)
返回false
,这明显违反了对称性。假设你将一个不区分大小写的字符串放到一个集合中:
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.
这时list.contains(s)
会返回什么?谁知道呢?在Sun当前的实现中,它碰巧会返回false
,但那仅是一种实现方案。在另一种实现中,它也可能很容易的返回true
或抛出一个运行时异常。一旦你违反了equals
约定,当面对你的对象时,你根本不指定其它的对象行为会怎样。
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:
为了消除这个问题,只要从equals
方法中移除与String
进行交互的,考虑不周的尝试即可。一旦你这样做了,你可以重构这个方法给它一个返回即可:
@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
约定的第三个要求是说如果一个对象等价于第二个对象,而第二个对象等价于第三个对象,则第一个对象等价于第三个对象。同样的,不难想象会无意中违反这个要求。考虑这样一种情况,子类添加一个新的值组件到它的超类中。换句话说,子类添加的信息会影响equals
比较。以一个简单的不可变的二维整数点类作为开始:
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:
假设你想扩展这个类,给点添加颜色的概念:
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
方法应该看起来是怎样的?如果一点也不修改,直接从Point
继承equals
方法,在进行equals
比较时颜色信息会被忽略。虽然这没有违反equals
约定,但很明显这是不可接受的。假设你写了一个equals
方法,只有在它的参数是另一个有色点,且它们具有相同的位置和颜色时才返回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:
这个方法的问题在于:当你比较一个普通点和一个有色点或相反的情况时,你可能会得到不同的结果。前者的比较忽略了颜色,而后者总是返回false
,因为参数类型不正确。为了使这个更具体一点,我们创建一个普通点和一个有色点:
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
进行比较混合比较时忽略颜色来修正这个问题:
// 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:
这个方法提供了对称性,但违反了传递性:
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.
现在p1.equals(p2)
和p2.equals(p3)
返回true
,而p1.equals(p3)
返回false
,很明显这违反了传递性。前两个比较忽略了颜色,而第三个比较考虑了颜色。
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.
因此解决方案是什么?事实证明:在面向对象语言中,等价关系问题是一个基本的问题。当保留equals
约定时,你无法在扩展一个实例化的类的同时添加值组件,除非你愿意放弃面向对象抽象的优势。
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:
你可能听说过你可以在equals
方法中通过使用getClass
测试代替instanceof
测试,从而在扩展一个可实例化的类并添加值组件的同时,保留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.
当且仅当它们具有相同的实现类时,上面的代码在比较对象时才会有效。虽然这不是很糟糕,但结果是不可接受的。
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:
假设我们想写一个方法来判断一个整数点是否在单位圆上。下面是一种写法:
// 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:
虽然这可能不是实现这个功能的最快方式,但它确实有效。但假设你以某种不添加值组件的方式扩展了Point
,例如通过它的构造函数来追踪创建了多少实例:
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
.
里氏替换原则认为,一个类型的任何重要属性也适用于它的子类型,因此该类型编写的任何方法在它的子类型中也都应该工作良好[Liskov87]。但假设我们给onUnitCircle
传递了一个CounterPoint
实例。如果Point
类使用了基于getClass
的equals
方法,onUnitCircle
将会返回false
,无论CounterPoint
实例的x
值和y
值是多少。这是因为集合,例如onUnitCircle
方法中的HashSet
,使用equals
方法来测试是否包含元素,没有CounterPoint
实例等于Point
。然而,如果你在Point
上使用合适的基于instanceof
的equals
方法,当面对CounterPoint
时,同样的onUnitCircle
方法会工作的很好。
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:
尽管没有令人满意的方式来扩展一个可实例化的类并添加值组件,但有一个很好的解决方案。遵循Item 16 “Favor composition over inheritance”的建议,不再让ColorPoint
继承Point
,而是通过在ColorPoint
中添加一个私有的Point
字段和一个公有的视图方法(Item 5),此方法返回一个与有色点具有相同位置的普通点:
// 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平台库中有一些类扩展了一个可实例化的类并添加了一个值组件。例如,java.sql.Timestamp
扩展了java.util.Date
并添加了一个nanoseconds
字段。Timestamp
的equals
实现确实违反了对称性,如果Timestamp
和Date
用在同一个集合中或混杂在一起,会引起不稳定的行为。Timestamp
类有一个免责声明,警告程序员不要混合日期和时间戳。虽然只要你将它们分开就不会有麻烦,但是没有任何东西阻止你混合它们,而且产生的错误很难调试。Timestamp
类的这个行为是一个错误,不应该进行模仿。
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.
注意,你可以添加值组件到抽象类的子类而且不会违反equals
约定。对于遵循Item 20 “Prefer class hierarchies to tagged classes”的建议而得到这种类层次来说,这是非常重要的。例如,你可以有一个没有值组件的抽象类Shape
,子类Circle
添加了radius
字段,子类Rectangle
添加了length
和width
字段。只要不能直接创建一个超类实例,上面的种种问题就不会发生。
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
约定的第四个要求是说如果两个对象相等,它们必须一致相等,除非其中一个(或二者)被修改了。换句话说,可变对象在不同的时间可以等于不同的对象而不可变对象不能。当你写了一个类,仔细想想它是否应该是不可变的(Item 15)。如果你推断它应该是不可变的,那么要确保你的equals
方法满足这样的约束条件:相等的对象永远相等,不等的对象永远不等。
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.
无论一个类是否是不可变的,都不要写一个依赖于不可靠资源的equals
方法。如果你违反了这个禁令,要满足一致性要求是非常困难的。例如,java.net.URL
的equals
方法依赖于对关联URL主机的IP地址的比较。将主机名转换成IP地址可能需要访问网络,随时间推移它不能保证取得相同的结果。这可能会导致URL equals
方法违反equals
约定并在实践中产生问题。(很遗憾,由于兼容性问题,这一行为不能被修改。)除了极少数例外,equals
方法应该对常驻内存对象进行确定性计算。
“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
:
“非空性”——最后的要求由于没有名字我称之为“非空性”,这个要求是说所有的对象都不等于null
。虽然很难想象调用o.equals(null)
会偶然的返回true
,但不难想象会意外抛出NullPointerException
的情况。通用约定不允许出现这种情况。许多类的equals
方法为了防止出现这种情况都进行对null
的显式测试:
@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:
这个测试是没必要的。为了平等测试其参数,为了调用它的访问器或访问其字段,equals
方法首先必须将它的参数转换成合适的类型。在进行转换之前,equals
方法必须使用instanceof
操作符来检查它的参数是否是正确的类型:
@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
方法传入了一个错误类型的参数,equals
方法会抛出ClassCastException
,这违反了equals
约定。但当指定instanceof
时,如果它的第一个操作数为null
,无论它的第二个操作数是什么类型,它都会返回false
[JLS, 15.20.2]。所以如果传入null
类型检查将会返回false
,因此你不必进行单独的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.
将上面所有的内容放在一起,下面是编写一个高质量equals
方法的流程:
使用
==
操作符来检查参数是否是这个对象的一个引用,。如果是,返回true
。这只是一个性能优化,如果比较的代价有可能很昂贵,这样做是值得的。使用
instanceof
操作符来检查参数类型是否正确。如果不正确,返回false
。通常,正确的类型是指equals
方法所在的那个类。有时候,它是这个类实现的一些接口。如果一个类实现了一个接口,这个接口提炼了equals
约定来允许比较那些实现了这个接口类,那么就使用接口。集合接口例如Set
,List
,Map
和Map.Entry
都有这个属性。将参数转换成正确的类型。由于转换测试已经被
instanceof
在之前做了,因此它保证能成功。对于类中的每一个“有效”字段,检查参数的这个字段是否匹配这个对象的对应字段。如果所有的这些测试都成功了,返回
true
;否则返回false
。如果第二步中的类型是一个接口,你必须通过接口方法访问参数的字段;如果类型是一个类,你可能要直接访问字段,依赖于它们的可访问性。
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.
对于基本类型,如果不是float
或double
,使用==
操作符进行比较;对于对象引用字段,递归地调用equals
方法;对于float
自动,使用Float.compare
方法;对于double
字段,使用Double.compare
。float
和double
字段的特别对待是有必要的,因为存在Float.NaN
,-0.0f
和类似的double
常量;更多细节请看Float.equals
。对于数组字段,对每个元素应用这些指导。如果数组中的每个元素都是有意义的,你可以使用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:
某些对象引用字段可能合理的包含null
。为了避免产生NullPointerException
的可能性,使用下面的习惯用法来比较这些字段:
(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
经常是等价的,使用下面的可替代方式可能会更快:
(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.
对于某些类而言,例如上面的CaseInsensitiveString
,字段比较比简单的相等性检测更复杂。如果是这种情况,你可能想存储这个字段的标准形式,因此equals
方法可以在这些标准形式上进行低开销的精确比较,而不是更高代码的非精确比较。这种技术最适合不可变类(Item 15);如果对象可以改变,你必须保持最新的标准形式。
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
方法的性能可能会受到字段比较顺序的影响。为了最佳性能,你首先应该比较那些更可能不同,比较代价更小的字段,或者理想情况下二者兼具的字段。你不能比较那些不属于对象逻辑状态一部分的字段,例如同步操作中的Lock
字段。你也不需要比较冗余的字段,它们能从“有意义字段”中计算出来,但这样做可能会改善equals
方法的性能。如果冗余字段相当于整个对象的概要描述,比较这个字段,如果失败的话会节省你比较真正数据的开销。例如,假设你有一个Polygon
类,并且你缓存这个区域。如果两个多边形有不同的面积,你就不需要比较它们的边和顶点。
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.当你完成了
equals
方法的编写时,问你自己三个问题:它是否是对称的?是否是可传递的?是否是一致的?并且不要只问你自己;编写单元测试来检查是否拥有这些属性!如果没有这些属性,弄清楚为什么没有,对应的修改equals
方法。当然你的equals
方法也必须满足其它两个属性(自反性和“非空性”),但这两个属性通常会自动满足。
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:
根据上述规则构建的equals
方法具体例子请看Item 9的PhoneNumber.equals`。下面是一些最后的警告:
Always override
hashCode
when you overrideequals
(Item9).当你重写
equals
时,总是重写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.不要试图自作聪明。如果你简单的测试字段的相等性,不难遵循
equals
约定。如果过度的追求等价关系,很容易陷入到麻烦中。考虑任何形式的别名通常不是一个好想法。例如,File
类不应该试图把指向同名的符号链接看作相等。所幸它没有这样做。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
对象替换为其它对象。对于程序员来讲,写一个equals
方法看起来像下面的一样是不常见的,并且花费了好几个小时都不明白它为什么不能正确工作:
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).
这个问题在于这个方法没有重写Object.equals
方法,Object.equals
方法的参数类型是Object
,但相反,它重载了equals
方法(Item 41)。除了正常的equals
方法之外,提供这样一个“强类型”equals
方法是可接受的,只要这两个方法返回同样的结果,但没有令人信服的理由去这样做。在某些特定环境下它可能会提供很小的收益,但相对于增加的复杂性来讲是不值得的(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
注解的一致使用会阻止你犯这个错误(Item 36)。这个equals
方法不能编译并且错误信息会确切告诉你错误是什么。
@Override
public boolean equals(MyClass o) {
...
}