覆盖equals时请遵守通用约定外文翻译资料

 2022-08-09 16:07:09

effective Java 第三章第一节

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:

bull; 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 by Object has exactly the right behavior for these classes.

bull; There is no need for the class to provide a “logical equality” test. For example, java.util.regex.Pattern could have overridden equals to check whether two Pattern instances represented exactly the same regular expression, but the designers didnrsquo;t think that clients would need or want this functionality. Under these circumstances, the equalsimplementation inherited from Object is ideal.

bull; A superclass has already overridden equals, and the superclass 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.

bull; The class is private or package-private, and you are certain that its equals method will never be invoked. If you are extremely risk-averse, you can override the equals method to ensure that it isnrsquo;t invoked accidentally:

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

So when is it appropriate to override equals? It is when a class has a notion of logical equality that differs from mere object identity and a superclass has not already overridden equals. This is generally the case for value classes. A value class is simply a class that represents a value, such as Integer or String. 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. 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 34) fall into this category. For these classes, logical equality is the same as object identity, so Objectrsquo;s equals method functions as a logical equals method. When you override the equals method, you must adhere to its general contract. Here is the contract, from the specification for Object : The equals method implements an equivalence relation. It has these properties:

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

bull; 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.

bull; 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.

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

bull; For any non-null reference value x, x.equals(null) must return 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.

Now that you are aware of the dangers of violating the equals contract, letrsquo;s go over the contract in detail. The good news is that, appearances notwithstanding, it really isnrsquo;t very complicated. Once you understand it, itrsquo;s not hard to adhere to it. So what is an equivalence relation? Loosely speaking, itrsquo;s an operator that partitions a set of elements into subsets whose elements are deemed equal to one another. These subsets are known as equivalence classes. For an equals method to be useful, all of the elements in each equivalence class must be interchangeable from the perspective of the user. Now letrsquo;s examine the five requirements in turn:

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

Symmetry—The second requirement says that any two objects must agree on whether they are equal. Unlike the first requirement, itrsquo;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 equals comparisons:

// Broken - violates symmetry!

public final class CaseInsensitiveString {

private final String s; public CaseInsensitiveString(String s)

{ this.s = Objects.requireNonNull(s); }

// Broken - violates symmetry!

@Override

public boolean equals(Object o) {

if (o inst

剩余内容已隐藏,支付完成后下载完整资料


effective Java 第三章第一节

覆盖equals时请遵守通用约定

覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误,后果可能很严重。最容易避免这类问题的办法就是不覆盖 equals 方法,在这种情况下,类的每个实例都只与它自身相等。如果满足了以下任何一个条件,这就正是所期望的结果:

bull;类的每个实例本质上都是唯一的。对于代表活动实体而不是值(value)的类来说确实如此,例如Thread。Object 提供的 equals 实现对于这些类来说正是正确的行为。

bull;类没有必要提供“逻辑相等”(logical equality)的测试功能。例如,java.util . regex . Pattern可以覆盖equals,以检查两个 Patter口实例是否代表同一个正则表达式,但是设计者并不认为客户需要或者期望这样的功能。在这类情况之下,从Object 继承得到的 equals 实现已经足够了。

bull;超类已经覆盖了equals,超类的行为对于这个类也是合适的。例如大多数的Se实现都从AbstractSet 继承equals实现,List实现从AbstractList继承equals实现,Map实现从AbstractMap继承equals实现。

bull; 类是私有的,或者是包级私有的,可以确定它的equals方法永远不会被调用。 如果你非常想要规避风险,则可以覆盖equals方法,以确保不会意外调用它:

@Override public boolean equals(Object o){throw new AssertionError(); //永远不会调用方法}

那么,什么时候应该覆盖equals方法呢?如果类具有自己特有的“逻辑相等”(logical equality)概念(不同于对象等同的概念),而且超类还没有覆盖 equals。 这通常属于“值 类”( value class)的情形。 值类仅仅是一个表示值的类,例如 Integer 或者 String。 程 序员在利用 equals 方法来比较值对象的引用时,希望知道它们在逻辑上是否相等,而不 是想了解它们是否指向同一个对象。 为了满足程序员的要求,不仅必须覆盖 equals 方法, 而且这样做也使得这个类的实例可以被用作映射表(map)的键(key),或者集合(set)的元 素,使映射或者集合表现出预期的行为。 有一种“值类”不需要覆盖 equals 方法,即用实例受控(详见第 l 条)确保“每个 值至多只存在一个对象”的类。 枚举类型(详见第 34 条)就属于这种类。 对于这样的类而 言,逻辑相同与对象等同是一回事,因此 Object 的 equals 方法等同于逻辑意义上的 equals 方法。

在覆盖 equals 方法的时候,必须要遵守它的通用约定。 下面是约定的内容,来自 Object 的规范。equals方法实现等价关系。它具有以下属性:

bull;自反性:对于任何非空参考值x,x.equals(x)必须返回true。

bull;对称性:对于任何非空参考值x和y,只有当y.equals(x)返回true时,x.equals(y)才必须返回true。

bull;传递性:对于x,y,z的任何非空参考值,如果x.equals(y)返回true,而y.equals(z)返回true,则x.equals(z)必须返回true。

bull;一致:对于任何非空参考值x和y,如果没有修改equals比较中使用的信息,则多次调用x.equals(y)必须始终返回true或始终返回false。

bull;对于任何非空参考值x,x.equals(null)必须返回false。

除非你对数学特别感兴趣,否则这些规定看起来可能有点让人感到恐惧,但是绝对不 要忽视这些规定!如果违反了,就会发现程序将会表现得不正常,甚至崩溃,而且很难找到失败的根源。用 John Donne 的话说,没有哪个类是孤立的。 一个类的实例通常会被频繁地传递给另一个类的实例。有许多类,包括所有的集合类(collection class)在内,都依赖于传递给它们的对象是否遵守了equals 约定。现在你已经知道了违反 equals 约定有多么可怕,下面将更细致地讨论这些约定。值得欣慰的是,这些约定虽然看起来很吓人,实际上并不十分复杂。 一旦理解了这些约定,要遵守它们并不困难。

那么什么是等价关系?不严格地说,它是一个操作符,将一组元素划分到其元素与另一个元素等价的分组中。这些分组被称作等价类(equivalence class)。从用户的角度来看,对于有用的 equals 方法,每个等价类中的所有元素都必须是可交换的。现在我们按照顺 序逐一查看以下 5 个要求。

自反性—第一项要求仅说明一个对象必须等于其自身。很难想象会无意间违反这一规定。如果您要违反它,然后将您的类的实例添加到集合中,contains方法可能会说该集合不包含您刚刚添加的实例。

对称性-第二个要求说,任何两个对象必须就它们是否相等达成一致。与第一个要求不同,不难想象会无意间违反此要求。例如,考虑以下类,该类实现了不区分大小写的字符串。字符串的大小写由toString保留,但在equals比较中忽略:

//损坏-违反对称性!

public final class CaseInsensitiveString {

private final String s; public CaseInsensitiveString(String s)

{ this.s = Objects.requireNonNull(s); }

//损坏-违反对称性!

@Override

public boolean equals(Object o) {

if(oinstanceofCaseInsensitiveString)

return s.equalsIgnoreCase( ((CaseInsensitiveString) o).s);

if (o instanceof String)

//单向互操作!

return s.equalsIgnoreCase((String) o);

return false;} ...

//省略余数}

此类中,意图良好的equals方法尝试与普通字符串进行互操作。假设我们有一个不区分大小写的字符串和一个普通的字符串:

CaseInsensitiveString cis = new CaseInsensitiveString(“ Polish”);

String s =“ polish”;

如预期的那样,cis.equals(s)返回true。问题在于,尽管CaseInsensitiveString中的equals方法了解普通字符串,但String中的equals方法却忽略了不区分大小写的字符串。因此,s.equals(cis)返回false,这明显违反了对称性。

假设您将不区分大小写的字符串放入集合中:

List lt;CaseInsensitiveStringgt; list = new ArrayList lt;gt;();

list.add(cis);

此时list.contains返回什么?没人知道。在当前的OpenJDK实现中,它恰巧返回false,但这只是一个实现工件。在另一个实现中,它可以轻松返回true或抛出运行时异常。一旦违反了equals约定,当其他对象面对你 的对象时,你完全不知道这些对象的行为会怎么样。

要解决此问题,只需从equals方法中删除与String互操作的错误构想。完成此操作后,可以将方法重构为单个return语句:

@Override

public boolean equals(Object o){

return o instanceof CaseInsensitiveString amp;amp;((CaseInsensitiveString)o).s.equalsIgnoreCase(s); }

传递性—equals合约的第三个要求说,如果一个对象等于第二个对象,而第二个对象等于第三个对象,则第一个对象必须等于第三个对象。同样,不难想象会无意间违反此要求。考虑一个子类的情况,该子类为其父类添加了新的值组件。换句话说,子类添加了一条影响等值比较的信息。让我们从一个简单的不变的二维整数point类开始:

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 amp;amp; p.y == y; }...

// 省略余数 }

假设你想要扩展这个类,为一个点添加颜色信息 :

public class ColorPoint extends Point {

private final Color color;

public ColorPoint(int x, int y, Color color) {super(x, y);

this.color = color; }...

// 省略余数}

equals 方法会是什么样的呢?如果完全不提供 equals 方法,而是直接从 Point 继承过来,在equals 做比较的时候颜色信息就被忽略掉了。虽然这样做不会违反equals 约定,但很明显这是无法接受的。假设编写了一个equals方法,只有当它的参数是另一个有色点,并且具有同样的位置和颜色时,它才会返回true :

//损坏-违反对称性!

@Override

public boolean equals(Object o) {

if (!(o instanceof ColorPoint)) return false;

return super.equals(o) amp;amp; ((ColorPoint) o).color == color;}

此方法的问题在于,将一个点与一个有色点进行比较时可能会得到不同的结果,反之亦然。前一个比较忽略颜色,而后一个比较始终返回false,因为参数的类型不正确。为了具体说明,我们先创建一个点和一个有色点:

Point p = new Point(1, 2);

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

然后p.equals(cp)返回true,而cp.equals(p)返回false。您可以尝试通过使ColorPoint.equals在进行“混合比较”时忽略颜色来解决该问题:

//损坏-违反传递性!

@Override

public boolean equals(Object o) {

if (!(o instanceof Point)) return false;

//如果o是正常点,则进行色盲比较

if (!(o instanceof ColorPoint)) return o.equals(this);

// o是一个ColorPoint;做一个完整的比较

return super.equals(o) amp;amp; ((ColorPoint) o).color == color; }

这种方法确实提供了对称性,但以传递性为代价:

ColorPoint p1 = new ColorPoint(1,2,Color.RED);

Point p2 = new Point(1, 2);

ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);

现在p1.equals(p2)和p2.equals(p3)返回true,而p1.equals(p3)返回false,这明显违反了传递性。前两个比较是“色盲”,而第三个比较考虑了颜色。

另外,此方法可能导致无限递归:假设有两个Point的子类,例如ColorPoint和SmellPoint,每个子类都具有这种equals方法。然后,对myColorPoint.equals(mySmellPoint)的调用将引发StackOverflowError异常。

那有什么解决方案?事实证明,这是面向对象语言中的等价关系的基本问题。

除非您愿意放弃面向对象抽象的好处,否则无法扩展可实例化的类并在保留equals合约的同时添加值成分。您可能会听到它说,您可以扩展一个可实例化的类并添加一个值组件,同时在equals方法中使用getClass测试代替instanceof测试来保留equals契约:

//损坏-违反了Liskov替代原则(第43页)

@Override

public boolean equals(Object o)

剩余内容已隐藏,支付完成后下载完整资料


资料编号:[238963],资料为PDF文档或Word文档,PDF文档可免费转换为Word

原文和译文剩余内容已隐藏,您需要先支付 30元 才能查看原文和译文全部内容!立即支付

以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。