`

读书笔记--Effective Java 2nd -- 第三章 对于所有对象都通用的方法

    博客分类:
  • java
阅读更多

第三章 对于所有对象都通用的方法

8 覆盖equals时请遵守通用约定

一般来说,没有逻辑相等的概念的时候都不要覆盖equals方法。或者说超类已经提供的符合子类equals的方法。当然,类时私有的也不应该覆盖。

覆盖equals方法的情况一般会是“值类(value class)”。覆盖equals方法时应该遵守它的通用约定:

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.

       翻译过来依次是自反性,对称性,传递性,一致性和对于任何非null引用值xx.equals(null)一定返回false

       对于自反性来说,因为很多其他类(比如集合类)通过equals判断是否持有这个对象,如果违反这一条的话,那么调用对应的是否存在这个对象的方法时将返回false。当然违背这条的反例还真不好找。

       对于对称性来说,反例就比较好举出了(书中代码):

// 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

}

       

传递性引用书中一句话: 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.

但是还是有一个方法解决子类附加组件而带来的传递性问题,就是用复合而非继承(附书中代码):

  

   // 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

}

 

       比较Point时可以使用asPoint方法返回Point视图。

      

       一致性呢,一句话概括下,无论类是否可变,都不要使equals方法依赖于不可靠的资源。

 

       书中总结出了实现高质量equals方法的诀窍:

       1. Use the == operator to check if the argument is a reference to this object.

2. Use the instanceof operator to check if the argument has the correct type.

3. Cast the argument to the correct type.

       4. For each significant field in the class, check if that field of the argument

matches the corresponding field of this object.

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.

对于null合法的域可以采用如下代码:

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

5. When you are finished writing your equals method, ask yourself three

questions: Is it symmetric? Is it transitive? Is it consistent?

 

还有一些告诫:

Always override hashCode when you override equals (Item 9).

Don’t try to be too clever.

Don’t substitute another type for Object in the equals declaration

 

9条 覆盖equals时总要覆盖hashcode

这一条主要是覆盖hashcode的时候一般采用如下的方法:

1. Store some constant nonzero value, say, 17, in an int variable called result.

2. For each significant field f in your object (each field taken into account by the

equals method, that is), do the following:

a. Compute an int hash code c for the field:

i. If the field is a boolean, compute (f ? 1 : 0).

ii. If the field is a byte, char, short, or int, compute (int) f.

iii. If the field is a long, compute (int) (f ^ (f >>> 32)).

iv. If the field is a float, compute Float.floatToIntBits(f).

v. If the field is a double, compute Double.doubleToLongBits(f), and

then hash the resulting long as in step 2.a.iii.

vi. If the field is an object reference and this classs equals method

compares the field by recursively invoking equals, recursively

invoke hashCode on the field. If a more complex comparison is

required, compute a canonical representation for this field and

invoke hashCode on the canonical representation. If the value of the

field is null, return 0 (or some other constant, but 0 is traditional).

vii. If the field is an array, treat it as if each element were a separate field.

That is, compute a hash code for each significant element by applying

these rules recursively, and combine these values per step 2.b. If every

element in an array field is significant, you can use one of the

Arrays.hashCode methods added in release 1.5.

b. Combine the hash code c computed in step 2.a into result as follows:

result = 31 * result + c;

3. Return result.

4. When you are finished writing the hashCode method, ask yourself whether

equal instances have equal hash codes. Write unit tests to verify your intuition!

If equal instances have unequal hash codes, figure out why and fix the problem.

 

还有一个印象比较深刻的是: Do not be tempted to exclude significant parts of an object from the hashcode computation to improve performance.因为这可能导致hashmap之类的性能很差。

      

       10 始终覆盖toString

       toString可以很好的表述一个类,在打log等等方面都非常有用。

       11 谨慎覆盖clone

       最好呢就是别去覆盖这个方法,需要复制的话可以使用拷贝构造器和静态拷贝工厂。

       12 考虑实现Comparable接口

       有一句话印象蛮深刻的: By implementing Comparable, you allow your class to interoperate with all of the many generic algorithms and collection implementations that depend on this

interface. You gain a tremendous amount of power for a small amount of effort.

       实现compareTo方法时从最关键的域开始比较,逐步进行到所有的重要域。

       这里顺便提一下书中的一个对compareTo的一个建议:

It is strongly recommended, but not strictly required, that (x.compareTo(y)

== 0) == (x.equals(y)). Generally speaking, any class that implements the

Comparable interface and violates this condition should clearly indicate this

fact. The recommended language is Note: This class has a natural ordering

that is inconsistent with equals.

如果违反了这个建议书中提到:

The final paragraph of the compareTo contract, which is a strong suggestion

rather than a true provision, simply states that the equality test imposed by the

compareTo method should generally return the same results as the equals

method. If this provision is obeyed, the ordering imposed by the compareTo

method is said to be consistent with equals. If its violated, the ordering is said to

be inconsistent with equals. A class whose compareTo method imposes an order

that is inconsistent with equals will still work, but sorted collections containing

elements of the class may not obey the general contract of the appropriate collection interfaces (Collection, Set, or Map). This is because the general contracts

for these interfaces are defined in terms of the equals method, but sorted collections

use the equality test imposed by compareTo in place of equals. It is not a

catastrophe if this happens, but its something to be aware of.

 

1
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics