Effective Java:Ch3_Methods:Item8_重写equals方法时遵循通用约定(三)

2014-11-24 11:59:54 · 作者: · 浏览: 132
ends 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(); }
}
然后我们将CounterPoint实例传入onUnitCircle方法中,则不管CounterPoint中x值和y值是什么,永远都只会返回false。因为CounterPoint中使用的集合框架(例如HashSet),使用equals方法来判断是否包含;而CounterPoint和Point是永远不会等价的。——不满足里氏替换原则!
但是,如果在Point的equals方法中使用instanceof,同样的onUnitCircle方法就会正常工作。
【解决办法1】
虽然没有令人满意的方法可以既扩展一个可实例化类,又增加一个值组件,不过有一个规避方法。根据Item16,“组合优于继承”,我们可以在ColorPoint中增加一个private的Point域,而不是扩展Point;同时提供一个public的视图方法,返回与该color point位置相同的Point对象:
[java]
// 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
}
Java平台库中,有一些类扩展了可实例化类,并增加了值 组件。【例】例如java.sql.Timestamp扩展了java.util.Date类并增加了nanoseconds域。Timestamp的equals方法就违反了对称性,如果Timestamp和Date被用于同一个集合中,或以其他什么方式混在一起使用,则会引起错误的行为。
Timestamp有一个免责声明,提醒程序员不要混用Date和Timestamp。虽然只要不混用他们就不会有麻烦,但是谁都不能阻止你混用他们,而结果导致的错误将会很难调试。Timestamp的这种行为是个错误,不值得效仿。
[java]
/**
* Note: This method is not symmetric with respect to the
* equals(Object) method in the base class.
*/
public boolean equals(java.lang.Object ts) {
if (ts instanceof Timestamp) {
return this.equals((Timestamp)ts);
} else {
return false;
}
}
注意,你可以在抽象类的子类中增加值组件,同时不违反equals约定。对于根据Item20(用类层次来替代标签类,Prefer class hierarchies to
tagged classes)而得到的类层次结构而言,这是非常重要的。【例】例如你可能有一个抽象类Shape,其中没有任何值组件;一个子类Circle,增加了一个radius域;一个子类Rectangle,增加了length和width域。上述问题就不会发生,因为不可能直接创建一个父类实例。
4)一致性(Consistent)
第四个要求是说如果两个对象相等,则他们在任何时候都必须相等,除非其中一个(或两个)对象被修改了。换句话说,可变对象在不同的时候可以和不同的对象相等,而不可变对象则不行。当你编写一个类时,仔细考虑考虑它是否应该是不可变的(Item15)?如果结论是应该不可变,则需要保证equals方法满足:相等的对象永远都相等,不等的对象永远都不等。
不论类是否可变,都不能让equals方法依赖于不可靠的资源。如果你违反了这个限制,那就很难满足一致性要求了。【例】例如java.net.URL的equals方法依赖于对URL中主机的IP地址的比较,而将主机名转译成IP地址需要访问网络,随着时间推移,并不保证能范围相同的结果。这就会导致URL的equals方法违反约定,并且已经在实践中引起问题了。不幸的是,由于兼容性需求,这一行为无法改变。除了少数例外情况,equals方法必须对驻留在内存中的对象进行确定性计算。
5)非空性(Non-Nullity)
最后一条要求的意思是所有对象都与null不相等。虽然很难想象什么情况下o.equals(null)会返回true,但却不难想象意外抛出NullPointException的情况,通用约定不允许出现这种情况。许多类的euqals方法使用null判断来防止这种情况:
[java]
@Override
public boolean equals(Object o){
if(o == null)
return false;
}
这种测试是不必要的。为了测试相等性,equals方法首先必须将参数强制转换为适合的类型,然后才能调用其访问器或域。在做强制转换之前,需要用instanceof方法来检查其参数是否是合适的类型:
[java]
@Override
public boolean equal